Compare commits

...

42 Commits

Author SHA1 Message Date
github-actions
9643ca35a3 GitHub bot: changelog [nobuild] 2026-09-11 09:46:01 +00:00
Alexandre
ce2d729e1b Update config.yaml 2026-09-11 11:37:14 +02:00
github-actions
8ce92d80e2 GitHub bot: changelog [nobuild] 2026-09-11 08:53:41 +00:00
Alexandre
d247ed88b4 Update CHANGELOG.md 2026-09-11 10:52:50 +02:00
Alexandre
c9b5c54545 Document breaking change in CHANGELOG
Added a breaking change note for admin ingress visibility.
2026-09-11 10:52:46 +02:00
Alexandre
07c6d1ca5d Document breaking change in CHANGELOG.md
Added a note about a breaking change for admins.
2026-09-11 10:52:21 +02:00
Alexandre
972f2c02b8 nobuild 2026-09-11 10:51:47 +02:00
Alexandre
58949cb09a nobuild 2026-09-11 10:51:44 +02:00
Alexandre
8043104f30 Update config.yaml 2026-09-11 10:43:26 +02:00
Alexandre
ced15122d9 Migrate legacy addon_config maps to app_config (#2937) nobuild
* Add one-shot app_config linter migration workflow

* Add app_config linter compatibility action

* Add add-on map compatibility normalizer

* Test add-on map compatibility normalizer

* Run app_config compatibility preparation on pull request

* Validate app_config short and long map forms separately

* Export validated workflow updates

* Use app_config-compatible linter for PR checks

* Use app_config-compatible linter for builds

* Remove temporary linter preparation workflow

* Add one-shot app_config manifest migration

* Migrate legacy addon_config maps to app_config

* Preserve upstream legacy aliases without deprecated literals

* Test legacy alias compatibility without deprecated literals

* Validate completed app_config migration

* Count only add-on manifests during migration validation

* Remove temporary migration validation workflow

* Document legacy alias construction

* Remove obsolete local linter compatibility script

* Remove obsolete local linter compatibility tests

* fix(pr-2937-review): address reviewer findings on app_config migration

- Remove the orphaned .github/actions/addon-linter composite action: it
  references .github/scripts/prepare_addon_lint_config.py, which was never
  added (the normalization approach was superseded by pinning
  frenck/action-addon-linter directly in #2936). Confirmed unused - no
  workflow invokes it.
- cleanuparr: HA_DATA_DIR pointed at /app_configs/cleanuparr, a path not
  mounted inside the container. cleanuparr's map is app_config:rw, which
  Supervisor mounts at /config, so data was living on the ephemeral
  container filesystem. Point it at /config instead (pre-existing bug,
  not introduced by this PR - just carried the wrong path forward).
- joplin/README.md: joplin's config.yaml still declares config:rw, not
  app_config (it was never part of the 86-manifest migration). Revert the
  doc text that incorrectly started calling it the app_config mapping.
- qbittorrent/00-folders.sh: fix a pre-existing copy/paste bug in the
  migration marker/log for the main-folder migration block - it referenced
  "openvpn" and a "qBitorrent" typo instead of qBittorrent.

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

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-09-11 09:30:49 +02:00
Alexandre
d60f17685e skill(hassio-addon-workflow): fix the stage-3 trap's actual mechanism (#3058)
* skill(hassio-addon-workflow): fix the stage-3 trap's actual mechanism

traps.md attributed the shipped svc-xorg failure to that service's own shebang
("starts #!/usr/bin/env bashio, not with-contenv"). Verified against
.templates/ha_entrypoint.sh: that isn't what decides it. ha_entrypoint.sh
rewrites the first line of every cont-init.d script and every services.d/s6-rc.d
run script to one shebang chosen by probing candidate_shebangs in order. The
first candidate is /command/with-contenv bashio; in add-ons that override the
base ENTRYPOINT ["/init"] (confirmed live in ente, wger, free_games_claimer),
s6 stage 1 never runs, that candidate fails, and the probe falls through to
/usr/bin/env bashio for every service in the image, regardless of what shebang
it shipped with. So no service reads stage 3 in that case, not just svc-xorg.

Surfaced independently by two agents in an unrelated eval run for PR #3057 (both
named the rewrite; neither had the conditionality right), then verified against
the script and confirmed the ENTRYPOINT-override path is real rather than
hypothetical before writing this.

No add-on touched; references/ only. markdownlint clean.

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

* skill(hassio-addon-workflow): scope the stage-3 trap to the PID-1-entrypoint path

Flagged by both Codex and Copilot, reproduced against .templates/ha_entrypoint.sh
before accepting.

The previous commit's fix still overclaimed: it said ha_entrypoint.sh rewrites
every service's shebang, universally. It doesn't. The services.d/s6-rc.d rewrite
loop is gated on `if $PID1` (line 430) — it only runs when ha_entrypoint.sh
itself is the container's PID 1, i.e. when the add-on replaces the base
ENTRYPOINT ["/init"]. Under the normal /init path (the overwhelming majority of
add-ons), ha_entrypoint.sh runs as the stage-2 hook with PID1=false, never
touches service run files, s6's own stage 1 already created
container_environment before any cont-init script ran, and a service's shipped
with-contenv shebang reads it normally — the opposite of what the previous
wording implied. cont-init.d scripts are unconditional either way
(run_one_script has no $PID1 gate), so a cont-init script's own shebang was
never informative and that half stands.

Also fixed: the previous commit named wger as a live example of the
ENTRYPOINT override. Copilot caught that wger's override is written into its
Dockerfile but commented out — confirmed (`grep '^#ENTRYPOINT' wger/Dockerfile`).
Only ente and free_games_claimer currently have it active; wger dropped from
the list.

markdownlint clean; no add-on touched.

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

---------

Co-authored-by: BirdNET-Go Addon Builder <addon-builder@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 08:05:29 +02:00
Alexandre
fc07a2d1b4 skill(hassio-addon-workflow): deduplicate, route traps.md by section, fix preflight path (#3057)
* skill(hassio-addon-workflow): deduplicate, route traps.md by section, fix preflight path

Reviewed the skill against Anthropic's skill-authoring guidance (progressive
disclosure, single ownership of each rule, explain the why rather than issue
rigid rules) with a second, independent pass from Codex (gpt-5.6-sol).

Real defect fixed: preflight.sh defaulted its repo argument to the hardcoded
/data/claude/hassio-addons, so when run from a worktree it inspected the main
checkout and reported that branch — the exact stale-checkout trap the script
exists to catch. It now defaults to `git rev-parse --show-toplevel`.

Deduplicated, one owner each:
- measurement methodology: evidence.md owns it; traps.md's Measurement section
  is gone (its rtk note moved to Environment and workspace)
- defensive-branch reachability: the standing rule owns it; step 3 points back
- subagent delegation: SKILL.md's delegation note owns it; codex-review.md points
- review-ratchet handling: SKILL.md step 6 owns it, absorbing codex-review.md's
  "this is wrong" vs "this is undefended" split
- post-merge revert mechanics: traps.md owns them; step 9 states the check only
- simplify checklist: step 5 owns the questions; simplify.md keeps the cases
- version format: CLAUDE.md owns it; traps.md keeps only what it does not say

Corrections found by review and verified against the workflows and scripts:
- Super-Linter runs on every PR (lint.yml `pull_request`), not weekly; it is
  continue-on-error at both call sites, which is the part that matters
- the three hard gates are matrixed over changed add-ons, so they skip rather
  than pass on a PR touching no top-level config.*
- `--sandbox read-only` blocks writes, not reads or command execution; Codex
  can and does fall back to fetching the repo from GitHub
- env_trace.sh's process argument is optional; pr_review.sh's reply and resolve
  take more than a PR number

Also: step 4 now routes to the traps.md section by anchor instead of asking for
the whole ~290-line file, and step 5 gains a Depth check (fix the shared
mechanism once more than one add-on hits it) adapted from the built-in
/simplify skill's altitude pass.

No new markdownlint findings; shellcheck clean; preflight.sh verified reporting
this worktree.

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

* skill(hassio-addon-workflow): make step 4's traps.md routing actually save the tokens

The routing table added in the previous commit was advisory only: a markdown
anchor cannot be loaded on its own, so an agent told to read
references/traps.md#shell-and-bashio reads the file and pays for all 18 KB.

Measured overpay per edit, against what each section actually needs:
  shell / bashio            642 B needed, 18,070 B paid  (28x)
  Dockerfile / arch       2,655 B needed, 18,070 B paid  (7x)
  base-image env option   3,070 B needed, 18,070 B paid  (6x)
"CI and review bots" is 6,302 B of that — 35% of the file — and is needed at
steps 7-8, never at step 4.

scripts/traps.sh prints one section. Scripts are run rather than read, so the
helper itself costs no prompt tokens and the table now buys what it claims.
Kept traps.md whole rather than splitting it into six files: the split would
save the same bytes but fragments a file with a working table of contents and
breaks the references/traps.md#anchor cross-references that steps 5, 7 and 9
still use.

Ambiguous or unmatched keywords list the sections and exit non-zero rather than
silently printing the wrong one. Verified: every keyword in the step 4 table
resolves to exactly one section, headings containing '/' work, and the last
section reaches EOF. shellcheck clean; no new markdownlint findings.

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

* skill(hassio-addon-workflow): fix review findings — PATH form, preflight fallback, config.json claim

All three reproduced before accepting.

Step 4's routing table used a bare `traps.sh <keyword>`, which is not on PATH:
every row failed with "traps.sh: command not found" from the repo root. Flagged
independently by Codex, Copilot and CodeRabbit. Now uses the
`bash "$SKILL/scripts/traps.sh"` form already established above it; all five
keywords verified to run and resolve to exactly one section.

preflight.sh fell back to the hardcoded /data/claude/hassio-addons when
`git rev-parse` could not answer, recreating the stale-checkout bug this commit
series set out to fix — and worse than CodeRabbit described: from a non-git
directory it reported an unrelated branch of the main checkout as though it were
the caller's own. It now refuses and asks for an explicit path (exit 1).
Explicit-argument and in-worktree behaviour unchanged, both re-verified.

Codex correctly noted the description advertised config.json while preflight.sh
and validate.sh parse only config.yaml. Exactly one add-on uses config.json
(zzz_archived_tor, archived) against 136 using config.yaml, so the proportionate
fix is Codex's own alternative — narrow the description — rather than teaching
two scripts JSON for an archived add-on. config.json dropped from the
description.

shellcheck clean; no new markdownlint findings.

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

---------

Co-authored-by: BirdNET-Go Addon Builder <addon-builder@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 22:42:55 +02:00
Alexandre
a3bba58229 fix(templates): install the stop handler before startup, not after it (#3054)
* fix(templates): install the stop handler before startup, not after it

`.templates/ha_entrypoint.sh` installed its SIGTERM/SIGINT trap at the very
end of startup, after `wait_for_supervisor` and after the whole
`/etc/cont-init.d/*` loop. 105 of the 136 add-ons here set `init: false`,
which makes this script namespace PID 1, and the kernel discards a signal
whose handler is still SIG_DFL. A stop arriving during startup was therefore
lost outright: Home Assistant waited out the grace period, the container died
on SIGKILL, and the add-on surfaced as Error.

The trap now goes in immediately after the PID1 detection block, still behind
an `if $PID1` guard so the 31 add-ons running under Docker's own init keep
their current signal behaviour. `terminate()` itself is unchanged apart from
indentation.

Measured on this file, run as PID 1 in a PID+mount namespace with a
bind-mounted `/etc/cont-init.d`:

  SIGTERM 3s into an 8s cont-init script
    before: never exits (alive >40s)   after: exits 5s later, script wrote 8/8
  SIGTERM before cont-init starts
    before: never exits (alive >40s)   after: exits 8s later, script wrote 8/8
  SIGTERM during 8 fast cont-init scripts
    before: never exits (alive >20s)   after: exits in 0s, cleanly after 6 of 8

Nothing is interrupted mid-write. Only PID 1 is signalled and bash defers a
trap to the next command boundary, so the in-flight command always reaps
first: a `dd` writing 16,384,000 bytes over ~5s took the entrypoint's SIGTERM
at 2s and still wrote all 16,384,000 bytes.

The residual is that a stop during one long-running cont-init script waits for
that script. Backgrounding the loop and killing the child would remove it, but
that truncates the child mid-write, and `01-config_yaml.sh` and
`19-json_repair.sh` both write non-atomically into the persisted `/config`.
Waiting was preferred over risking a user's config.

omni-tools is bumped so this gets a CI build and the fix reaches the add-on
the report came from; every other add-on picks it up on its next rebuild.

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

* fix(templates): make terminate() errexit-safe before it can run early

Moving the trap to the top of the script exposed a latent hazard that only
existed once the handler could fire before the application was up.

`validate_shebang()` leaves `set -e` on in the outer shell, so `terminate()`
runs under errexit -- measured: the shell flags at the cont-init stage are
`ehB`. Under errexit the first failing command in a trap handler aborts the
handler and exits with that status, skipping the child-kill loop and the
`exit 0`.

Every command in the stock body is already guarded with `|| true`, `|| echo`
or an `if`, so this was inert. But postgres_15 and postgres_17 patch this
function at build time (both `Dockerfile:77`):

  sed -i "/Termination signal received/a gosu postgres pg_ctl -D \"$PGDATA\" -m fast stop"

That command is unguarded, and it fails whenever the stop arrives before the
database is up. With the trap installed late, that never happened: postgres was
already running by the time the handler existed. With the trap installed early
the window is real -- it spans `wait_for_supervisor` (up to 30s) and the whole
cont-init chain, which for postgres_15 includes a `chown -R` over `$PGDATA`.

Replaying that exact sed against the patched entrypoint and stopping during
cont-init:

  without set +e   exit 127, handler aborted before killing any child
  with set +e      exit 0,   handler ran to completion

Exiting non-zero on a stop is what Home Assistant reports as Error, so without
this the change would have reproduced the reported symptom for those two
add-ons in a narrower window.

`set +e` states the intent the guarded body already implies, and covers the
injected line without postgres_15/17 needing an edit. They are the only two
add-ons that patch terminate(); the other six Dockerfiles that sed the
entrypoint touch other regions.

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

---------

Co-authored-by: BirdNET-Go Addon Builder <addon-builder@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 14:15:44 +02:00
Alexandre
af2736a170 docs(birdnet-go-dev): document the first-daily-consensus setting (#3056)
* docs(birdnet-go-dev): document the first-daily-consensus setting

Adds a "Fork-only settings" section to the add-on README covering the
first-daily-detection consensus rule from alexbelgium/birdnet-go#63,
which this build compiles in because merge-prs.sh merges every open
non-draft PR on the fork.

The section leads with what the setting does NOT affect, because
"requires two models to confirm" reads as far more invasive than it is:
it is inert on a single-model install (the default), on bats and
non-bird sound classes, on species not known to every active bird model,
and when a dynamic threshold has already lowered the bar. Without that
list a user reasonably concludes the add-on will start dropping
detections.

Also qualifies the header's "everything below is identical to the
standard add-on" claim, which the new section would otherwise contradict.

Docs only; no add-on behaviour changes. Version bumped so Supervisor
offers the rebuild.

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

* docs(birdnet-go-dev): correct the first-daily-consensus wording

Copilot, Codex and CodeRabbit each independently flagged the same three
inaccuracies on PR #3056. Verified each against merge-prs.sh and the
alexbelgium/birdnet-go implementation before fixing.

- The "disappears once merged upstream" claim was backwards.
  merge-prs.sh syncs the fork's main with upstream BEFORE applying open
  PRs, so a PR that merges upstream stays in every future build via that
  sync step - it only stops being fork-only. Only a PR closed without
  merging drops out.
- "Every later detection... behaves exactly as today" undersold the
  rule. Every attempt for a species is held to the two-model bar until
  one is accepted, not just the first attempt - confirmed by reading
  firstDailyGateApplies, which re-evaluates every detection against the
  same memo until acceptedToday is true.
- "known to every active bird model" is decided per audio source
  (speciesSharedByActiveBirdModels scopes to sourceModelIDs, which comes
  from AnalysisBuffers(sourceID)), not add-on-wide across every source.

Docs only. validate.sh passes, no new lint findings.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-10 07:37:33 +02:00
github-actions
d31ccfa21c GitHub bot: changelog [nobuild] 2026-09-09 19:54:08 +00:00
Alexandre
9c277876ff Update config.yaml 2026-09-09 21:47:42 +02:00
github-actions
7e59aa8803 GitHub bot: changelog [nobuild] 2026-09-09 12:53:50 +00:00
Alexandre
360fd669fa Update config.yaml 2026-09-09 14:47:48 +02:00
github-actions
7bf77c8558 GitHub bot: changelog [nobuild] 2026-09-09 11:28:42 +00:00
Alexandre
a195d3ac39 Update version to 20260909.3 in config.yaml 2026-09-09 13:22:38 +02:00
github-actions
14834d4063 GitHub bot: changelog [nobuild] 2026-09-09 09:18:02 +00:00
Alexandre
f5caafd62c Update config.yaml 2026-09-09 11:12:33 +02:00
github-actions
fd61631ccd GitHub bot: changelog [nobuild] 2026-09-09 08:12:04 +00:00
Alexandre
578a7f8368 Update config.yaml 2026-09-09 10:06:30 +02:00
Alexandre
5115dcb93a Update config.yaml 2026-09-09 10:06:18 +02:00
github-actions
fa5e890c4c GitHub bot: changelog [nobuild] 2026-09-08 13:44:27 +00:00
Alexandre
addb0ca66f Update config.yaml 2026-09-08 15:38:17 +02:00
dependabot[bot]
16e112b21f build(deps): bump anthropics/claude-code-action from 1.0.210 to 1.0.216 (#3055)
Bumps [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) from 1.0.210 to 1.0.216.
- [Release notes](https://github.com/anthropics/claude-code-action/releases)
- [Commits](a874e9ecd7...d75b94d5ad)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-08 15:35:47 +02:00
BirdNET-Go Addon Builder
040a4e0f2f birdnet-go-dev: rebuild 20260908.1 (upstream sync, 4 commits; fork PR #62) 2026-09-08 15:00:11 +02:00
alexbelgium
f72f7bef0f birdnet-go-dev: rebuild 20260908 (upstream sync, 7 commits) 2026-09-08 09:34:22 +02:00
BirdNET-Go Addon Builder
c18f45d822 birdnet-go-dev: rebuild 20260907 (fork PR #57 updated) 2026-09-07 14:39:59 +02:00
Alexandre
abf3d76873 birdnet-go-dev: add merge-prs.sh --check to catch build-breaking PR conflicts (#3053)
* birdnet-go-dev: add merge-prs.sh --check to catch build-breaking PR conflicts

The add-on build merges every open non-draft fork PR onto an upstream-synced
main. GitHub's `mergeable` field answers a different question: it compares a PR
against its *own* base ref, which for the stacked dashboard PRs is another
feature branch - sometimes one belonging to an already-closed PR. So a PR can
read MERGEABLE/CLEAN and still fail the build.

That is how run 34101694542 broke: PR #57 is MERGEABLE against the frozen branch
of closed PR #56, but conflicts with main on DetectionCard.svelte.

--check replays the exact same merge sequence, skips past conflicts instead of
stopping at the first one, and reports every offender as

    !!! CONFLICT pr=#N conflicts-with=<main|accumulated> files=... title=...

conflicts-with is probed in a throwaway worktree against the pristine synced
main, so it separates a PR that is merely stale (fixable in its own branch) from
one that only clashes with another open PR (needs a cross-PR decision).

Build behaviour is unchanged; --check is a no-op unless asked for, so no version
bump.

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

* birdnet-go-dev: fix --check misclassification, arg handling and temp leak

Review findings on #3053, all reproduced against git fixtures before fixing.

1. The probe did not apply the build's own merge policy (Codex).
   The real merge treats a *sole* frontend/package-lock.json conflict as
   non-fatal, but merges_onto_main() did a raw merge. A PR whose only clash
   with main is the generated lockfile was therefore reported as
   conflicts-with=main, which prints "the PR is stale against main; merge main
   into its branch" when in truth it merges onto main fine and only clashes
   with another open PR — the exact opposite remediation, from the feature
   whose entire job is to say which one it is.

   Extracted the policy into resolve_sole_lockfile() and routed both the real
   merge and the probe through it, so the two cannot drift apart again.

2. A second positional operand silently won (CodeRabbit).
   `merge-prs.sh a b` ran against b, where the pre-flag script used "${1}".
   A stray argument would have cloned into the wrong directory. Now exits 64.

3. mktemp parent directory leaked (CodeRabbit, Copilot).
   probe="$(mktemp -d)/probe" and only the child was removed, leaking one
   empty dir per checked conflict. Measured 3 leaked dirs over 3 calls; now 0.

Verified with throwaway repos: a lockfile-only clash now classifies as
"accumulated" (was "main"), a real source conflict with main still classifies
as "main", a clean PR still classifies as "accumulated", 0 leaked temp dirs,
0 stray worktrees, and the argument matrix behaves. shellcheck clean.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: BirdNET-Go Addon Builder <addon-builder@users.noreply.github.com>
2026-09-07 14:05:42 +02:00
Alexandre
bcde474c15 skill(hassio-addon-workflow): stop pr_review.sh watch reporting a false pass (#3052)
* skill(hassio-addon-workflow): stop pr_review.sh watch reporting a false pass

`gh pr checks` emits TAB-separated columns, but `watch` parsed it with awk's
default field splitting. Every check name containing a space was truncated to
its first word and the state column was never read:

    Codacy Static Code Analysis<TAB>fail   ->  Codacy=Static
    Addon linting (wger)<TAB>pass          ->  Addon=linting
    Test addon build (wger)<TAB>pending    ->  Test=addon

All three blocking gates have multi-word names, so the `case` matched neither
*fail* nor *pending* and fell through to "settled - all passing". That is a
false pass from the one command whose job is to report CI truthfully: #3044
was called green with Codacy red, and #3042 was called green while the HA
add-on linter was failing. A build that had not started would also have read
as a pass.

- parse with `awk -F'\t'`
- judge the state column alone, never the joined name=state text, so a check
  named e.g. `flaky-fail-detector` cannot read as a failure
- allowlist the good states (pass/skipping/pending) and treat anything
  unrecognised as a failure, so a new state cannot reach the passing branch
- name the checks that failed instead of only saying FAILURES
- keep waiting when only advisory checks have reported

Codacy is red on essentially every add-on PR here (#3019, #3044 and #3050 all
merged with it failing; master has no branch protection), so it is excluded
from the verdict but printed every poll and called out explicitly on settle.
Agreed with the maintainer. It is a denylist of known noise rather than an
allowlist of gates, so a job added to CI later counts as blocking by default.

Verified against real PRs: #3042 (blocking linter failure) now exits 1 and
names the check where it previously exited 0; #3018/#3019/#3044/#3050 report
correctly; pending, advisory-only, unknown-state and empty-output cases
checked against a stubbed gh. shellcheck clean.

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

* skill: record PR number in the traps entry

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

* skill: address review — skipped-checks wording, document TSV contract

CodeRabbit (correct): the header claimed exit 0 means "every blocking check
passed", but a skipped gate also yields 0. A PR touching no add-on skips all
three gates, so that wording overstated what a 0 means. Reworded; the runtime
warning about skipped jobs was already there.

Copilot recommended switching to `gh pr checks --json`. Not applied: that flag
does not exist before gh 2.36 and 2.23 ships in this add-on, where it fails
with `unknown flag: --json`. Its premises are also wrong for the path the
script takes — piped output carries no header and uses real tabs; the aligned
ANSI table is the TTY renderer, which $(... | awk) never gets. Documented the
non-TTY contract and the gh-version constraint in the comment and traps.md so
this is not "corrected" back into a break later.

The underlying worry — a format change reintroducing a false pass — is already
answered by the allowlist design, now verified explicitly: a header row lands
in the failure branch (exit 1) and a space-aligned table parses to zero rows,
so watch keeps waiting. Neither can return 0.

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

---------

Co-authored-by: BirdNET-Go Addon Builder <addon-builder@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 13:23:28 +02:00
GitHub Actions
15b6736818 Revert "birdnet-go-dev: rebuild after upstream sync and conflict fixes in fork PRs #6 and #36"
This reverts commit 96526dc31f.
2026-09-07 08:42:55 +00:00
alexbelgium
96526dc31f birdnet-go-dev: rebuild after upstream sync and conflict fixes in fork PRs #6 and #36 2026-09-07 10:38:44 +02:00
Alexandre
89654e4f13 fix(omni-tools): let the add-on stop by backgrounding nginx and running the entrypoint as PID 1 (#3050)
The add-on could not be stopped: Home Assistant showed an Error status
after a few seconds and the container kept running and still served the
web UI (#3049).

cont-init.d/99-run.sh started nginx in the foreground -- '&>' is a
redirect, not a background operator -- and ha_entrypoint.sh runs every
cont-init.d script sequentially in the foreground. That script therefore
never returned, so the entrypoint never reached the code that installs
the terminate() handler forwarding SIGTERM to the application. The
reporter's log shows both halves of this: it prints 'Starting custom
scripts' and never reaches 'Everything started!'.

The add-on also shipped no 'init:' key, so Supervisor's default of true
made Docker inject its own init as PID 1 and left ha_entrypoint.sh as
PID 2, where the 'if $PID1' block holding the trap is skipped outright.

Background the launch and set init: false. The script then returns, the
entrypoint installs its trap, and nginx -- orphaned by the exiting
script -- is reparented to the entrypoint as PID 1, where terminate()'s
'pgrep -P $$' finds it and signals it directly.

Backgrounding from cont-init.d is what 24 other add-ons here already do
(autobrr runs a bare 'nginx &'). Moving the launch to services.d was
considered and rejected: ha_entrypoint.sh runs each services.d/*/run
inside a restart subshell, so the application ends up a grandchild of
PID 1 while terminate() enumerates direct children only. Reproduced --
the app survives that path unsignalled -- and it is the larger change.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 10:25:33 +02:00
github-actions
607487cfb8 Github bot : image compressed 2026-09-06 23:12:31 +00:00
github-actions[bot]
6c5e2c171a Update stargazer map & cache 2026-09-06 00:55:35 +00:00
GitHub Actions
c6a55ea5f4 Revert "fix(linkwarden): stop installing packages Debian 11 no longer serves (#3047)"
This reverts commit 296bb3767a.
2026-09-05 05:51:42 +00:00
Alexandre
a237a6820c fix(wger): set the database env vars the upstream image stopped shipping (#3044)
* fix(wger): set the database env vars the upstream image stopped shipping

wger/server:latest no longer defines DJANGO_DB_ENGINE or DJANGO_DB_DATABASE in
its image environment, and upstream settings/main.py reads both with no
fallback. Every fresh install therefore died at startup with
"ImproperlyConfigured: Set the DJANGO_DB_ENGINE environment variable".

Set both explicitly in the Dockerfile, pointing at the sqlite database in
/data/database.sqlite that the add-on already persists, and add
DJANGO_PERFORM_MIGRATIONS=True so an existing database picks up new migrations
when the image is rebuilt against a newer upstream release.

With the path now set through the environment, the cont-init rewrite of the
database path in the Python settings is dead code — upstream no longer
hardcodes /home/wger/db/database.sqlite anywhere, so it only logged a warning.

Also move the add-on to the addon_configs location, as the issue asks: the
shared 01-config_yaml.sh template migrates an existing
/homeassistant/addons_config/wger/config.yaml on the first start.

Fixes #3043

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

* docs(wger): use the ISO date format the rest of this CHANGELOG uses

The 2.6.4 heading was written 04-09-2026 while every other dated heading
in this file, and 22937 of the 23999 dated headings in the repo, use ISO
YYYY-MM-DD. Copilot flagged the inconsistency on #3044.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 07:49:00 +02:00
Alexandre
296bb3767a fix(linkwarden): stop installing packages Debian 11 no longer serves (#3047)
* fix(linkwarden): stop installing packages Debian 11 no longer serves

The 2.16.2 updater build failed in the first RUN layer:

  E: Failed to fetch .../sudo_1.9.5p2-3%2bdeb11u4_amd64.deb  404  Not Found
  E: Failed to fetch .../vim-runtime_8.2.2434-3%2bdeb11u3_all.deb  404  Not Found

Debian 11 reached LTS end on 2026-08-31. Its bullseye-security index is frozen
at that date and still lists debs that deb.debian.org no longer serves; sudo is
one of them and still 404s on every deb.debian.org edge checked today, so the
build fails deterministically rather than transiently. Every package in
postgresql-16's own dependency chain that comes from bullseye-security was
checked and does fetch, so removing this first install unblocks the build.

None of the four packages is needed:

  - vim was never used by the add-on.
  - gnupg2 was only there for "gpg --dearmor"; apt reads the ASCII-armoured key
    from /etc/apt/trusted.gpg.d/postgresql.asc directly.
  - lsb-release was only there for "lsb_release -cs"; /etc/os-release carries
    VERSION_CODENAME.
  - sudo is replaced by su in the Postgres bootstrap, which is what the ente and
    postgres_15 add-ons already use for the same job.

curl is already present in the upstream linkwarden image, so no install step is
needed before the PGDG repository is configured.

The su rewrite keeps the argv psql receives identical. Because "su -" starts a
login shell, the service call now uses an absolute path (the login PATH has no
/usr/sbin) and the bootstrap SQL is written to and read from /tmp rather than
the script's working directory.

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

* fix(linkwarden): fetch bullseye-security from its origin, not the CDN

Dropping vim/gnupg2/lsb-release/sudo got the build past the first RUN, but
"apt-get install -y postgresql-16" then 404'd on its own dependencies, on arm64:

  E: Failed to fetch .../glibc/libc-l10n_2.31-13%2bdeb11u14_all.deb          404
  E: Failed to fetch .../exim4/exim4-base_4.94.2-7%2bdeb11u6_arm64.deb       404
  E: Failed to fetch .../python3.9/libpython3.9-minimal_3.9.2-1%2bdeb11u7_arm64.deb  404

All three are 200 on security.debian.org, the origin that deb.debian.org is a
CDN alias for. The rot is per-file and moves: exim4-base was 404 during the
build and 200 minutes later, so retrying is a coin flip rather than a fix.

Rewrite the security suite in /etc/apt/sources.list to security.debian.org
before "apt-get update". The main suite is left on the CDN; it is intact, and
bullseye main is already on archive.debian.org whereas bullseye-security is not.

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

* fix(linkwarden): feed the bootstrap SQL on stdin, and fix two comments

Review follow-up on the temp file, the Dockerfile comment and the CHANGELOG
wording.

The bootstrap SQL no longer goes through a file at all. Both reviewers objected
to the predictable root-written /tmp path; passing the statements to psql on
stdin removes the file rather than defending it, and is less code than either
the version being reviewed or the suggested mktemp. It also restores what the
original did before this branch: sudo ran "cat file | psql", so psql read the
statements from stdin then too.

The Dockerfile comment said "PGDATA repository" where it meant the PGDG apt
repository; PGDATA is the data-directory env var set two lines above, so the
wording was actively misleading.

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

* chore: record the shipped upstream release in updater.json

Each PR publishes an upstream version the updater bot had already selected
before CI reverted its commit, but updater.json still recorded the previous one.
The updater reads upstream_version as CURRENT and enters its update path
whenever it differs from the latest tag, so its next run would process the same
release again and derive a synthetic trailing-.1 version, producing a redundant
release, a duplicate CHANGELOG entry and a wasted build.

These values are exactly what the bot itself wrote in the reverted commit; this
restores its own record for a release now being shipped rather than choosing a
new one.

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

* fix(linkwarden): keep the Postgres password out of process arguments

CodeRabbit flagged that the password appears in the command string of the
password-setting call, and that the database-creation call reaches Postgres over
a TCP URI carrying the same password with sslmode=prefer. Both predate this
branch, but both lines are touched here.

Sending each statement to psql on stdin removes the password and the URI from
argv, and is shorter than either form it replaces: the escaped-quote nesting on
the ALTER USER call disappears with it.

The connection method is unchanged for the ALTER USER call, which already went
over the local socket as the postgres user. The database-creation call moves
from TCP to that same socket. This is safe by construction rather than by
assumption: the ALTER USER call runs first under "set -e" with no "|| true", so
the container cannot reach the second call unless socket access already worked.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 07:47:54 +02:00
Alexandre
62bbecb465 fix(mealie): build the frontend with pnpm and the upstream lockfile (#3046)
* fix(mealie): build the frontend with pnpm and the upstream lockfile

Upstream mealie migrated frontend/ from yarn to pnpm in v3.24.0 and deleted
frontend/yarn.lock. Our builder stage kept running "yarn install
--frozen-lockfile", which silently degraded to a fresh, unpinned resolution of
every dependency. That worked until a newer vuetify 4.x release dropped the
"vuetify/labs/rules" entry point, at which point "nuxt generate" failed with:

  Rolldown failed to resolve import "vuetify/labs/rules" from
  "virtual:nuxt:.nuxt%2Fvuetify-nuxt-plugin.client.mjs"

and the v3.25.1 updater build was reverted.

Mirror upstream's docker/Dockerfile frontend stage instead: node:24, a global
pnpm@11, and "pnpm install --frozen-lockfile" against the committed
pnpm-lock.yaml, so the dependency set is the one upstream tests. Also copy the
frontend tree with "cp -a frontend/." so dotfiles such as .nuxtignore come
across, and shallow-clone the tag.

Bumps the add-on to v3.25.1, the version the updater bot could not build.

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

* chore: record the shipped upstream release in updater.json

Each PR publishes an upstream version the updater bot had already selected
before CI reverted its commit, but updater.json still recorded the previous one.
The updater reads upstream_version as CURRENT and enters its update path
whenever it differs from the latest tag, so its next run would process the same
release again and derive a synthetic trailing-.1 version, producing a redundant
release, a duplicate CHANGELOG entry and a wasted build.

These values are exactly what the bot itself wrote in the reverted commit; this
restores its own record for a release now being shipped rather than choosing a
new one.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 07:47:32 +02:00
415 changed files with 934 additions and 488 deletions

View File

@@ -4,29 +4,30 @@ description: >-
Workflow for alexbelgium/hassio-addons Home Assistant add-on work: diagnose with real
measurements, independent Codex review, implement, open a PR, resolve CodeRabbit / Copilot /
Codex bot review comments, verify in production. Use for any task touching an add-on in this
repo — bugs, RAM/CPU/performance tuning, Dockerfile, config.yaml, cont-init.d or s6 changes,
version bumps, opening or iterating PRs — and when asked to "check with codex", "verify with
chatgpt", or resolve bot comments. Cheap for small asks: a light path skips the heavy steps.
repo — bugs, RAM/CPU/performance tuning, Dockerfile, config.yaml, build.json,
updater.json, cont-init.d, services.d or s6 changes, version and CHANGELOG bumps, a failing
add-on CI check, opening or iterating PRs — and, on an add-on task, when asked to "check with
codex", "verify with chatgpt", or resolve bot comments. Cheap for small asks: a light path skips
the heavy steps.
---
# Home Assistant add-on workflow
**Answer style.** Chat replies are terse: no pleasantries, no tool-call narration, no decorative
tables or emoji, no dumped logs quote the shortest decisive line, and don't re-read or re-print
what is already in context. Fragments and dropped articles are fine. Never compressed: uncertainty
markers ("likely", "assumed", "not verified"), negations (`not`/`never`/`no`/`only`), numbers,
units, technical terms, code blocks, error strings — step 9's Verified/Checked/Assumed distinction
outranks brevity every time. Write in full prose, not fragments, for security warnings,
irreversible-action confirmations, and any multi-step sequence a fragment could make ambiguous.
Persisted text is prose too: commits, CHANGELOG entries, PR bodies, review-thread replies, the
step 10 report.
**Answer style.** Chat replies are terse no pleasantries, tool-call narration, decorative tables,
emoji or dumped logs; quote the shortest decisive line and don't re-print what is already in
context. Telegraphic fragments are fine *there*. Two things outrank brevity, because dropping a
word from either changes the meaning rather than shortening it: never compress uncertainty markers
("likely", "assumed", "not verified"), negations, numbers, units, technical terms, code or error
strings — step 9's Verified/Checked/Assumed distinction wins every time; and write full prose
wherever a fragment could be read two ways — security warnings, irreversible-action confirmations,
multi-step sequences, and everything persisted (commits, CHANGELOG entries, PR bodies, review-thread
replies, the step 10 report).
Triage first, then one of two paths:
- **Light** — typo/doc fixes, CHANGELOG edits, version bumps, one-file edits at ladder levels
1-3 (below), simple questions: scope → implement → validate (`$SKILL/scripts/validate.sh
<addon> --vs-master`; `$SKILL` defined below) → PR (version bump + CHANGELOG still required) →
resolve bot comments.
1-3 (below), simple questions: scope → implement → validate (step 4) → PR (version bump +
CHANGELOG still required) → resolve bot comments.
- **Full loop** — performance/RAM/CPU work, diagnosis, anything changing a shipped default,
ladder levels 4-6, or an explicit Codex-check request: scope → measure → plan → Codex reviews
the plan → implement → simplify → Codex reviews the code → **simplify again** → PR → resolve
@@ -47,9 +48,8 @@ reasoning about a hypothetical *host* either. A defensive branch is complexity l
the input that reaches it and the image or host where that happens, or delete it and let the case
fail visibly instead.
**Repo layout.** `alexbelgium/hassio-addons`; each add-on is a top-level directory. This skill is
checked in at `.claude/skills/hassio-addon-workflow/` (canonical copy). Set the skill root once,
then every `scripts/…` and `references/…` path below is relative to it:
**Skill root.** The canonical copy lives at `.claude/skills/hassio-addon-workflow/`. Set it once;
every `scripts/…` and `references/…` path below is relative to it:
```bash
SKILL="$(git rev-parse --show-toplevel)/.claude/skills/hassio-addon-workflow"
@@ -63,9 +63,10 @@ bash "$SKILL/scripts/preflight.sh" # and likewise for the other scripts
**Delegate heavy output to a subagent.** Codex reviews and multi-thread PR triage produce output
you don't need verbatim in your own context. For Codex's plan review (step 3), Codex's code
review (step 6), and PR-comment listing when there are more than ~5 threads (step 8): launch a
subagent to run the command and report back only the objections/findings and your assessment of
each, not the raw transcript.
review (step 6), and PR-comment listing when there are more than ~5 threads (step 8): if subagents
are available, launch one to run the command and report back only the objections/findings and your
assessment of each, not the raw transcript. Otherwise redirect the output to a file and read the
parts you need.
---
@@ -85,9 +86,11 @@ Measure the running add-on rather than reasoning from source (`$BUILD_VERSION` s
- Is this flag/driver/package actually present? → inspect the artifact: `/proc/<pid>/cmdline`,
`command -v`, `/var/log/apt/history.log`
Verify you're reading the right revision first — `scripts/preflight.sh` catches a stale branch
before it costs a full analysis pass. Measurement methodology, gotchas, and real failure examples:
`references/evidence.md`.
Verify you're reading the right revision first — `scripts/preflight.sh` compares the checkout
against the running `$BUILD_VERSION`, which catches the usual stale branch before it costs a full
analysis pass (a version match does not prove the source is identical). Read
`references/evidence.md` before interpreting any number you did not get straight from
`measure.sh`, and for the failure modes this step exists to prevent.
## 3. Plan — choose the mechanism level, then Codex reviews it (full loop)
@@ -117,45 +120,60 @@ Attack your own plan before implementing:
- What am I **inferring** that I could instead **detect at runtime** or **record explicitly**?
Highest-yield question here — see `references/evidence.md`'s failure-mode section.
- For every branch that exists **only to survive something going wrong**: name the image or host
where that input actually arrives, and go and look. Naming is the bar, not reproducing it here —
`references/simplify.md` works the `/dev/shm` guard and the `s6-dumpenv` fallback through that
distinction.
where that input arrives (the standing rule above) and go and look, before you write it.
Full loop only, before writing code: get Codex's independent read on the plan. Spawn a subagent
whose prompt includes the path `references/codex-review.md` and tells it to follow that file's
invocation, then report back only Codex's objections and an assessment of each — not the raw
transcript.
Full loop only, before writing code: get Codex's independent read on the plan, delegated as above —
`references/codex-review.md` has the invocation and how to write the prompt.
## 4. Implement
Touching a shell script, Dockerfile, or env option? Read `references/traps.md` first — skim the
headings, read the sections you're about to touch; the bashio, s6-env, arch-guard and versioning
traps are all live. (The light-path facts it holds — versioning format, CHANGELOG heading — are
already inline in step 7.) Validate with `scripts/validate.sh <addon> --vs-master`. Write
behavioural tests for anything with branches, targeting **the regression a reviewer described**,
not just the happy path.
Read the `references/traps.md` section matching what you're about to touch. It is ~18 KB and all
but one section is irrelevant to any given edit, so print the one you need rather than reading the
file — run it with no argument to list the sections:
| Touching | Run |
| --- | --- |
| an option or anything a base-image service reads | `bash "$SKILL/scripts/traps.sh" passing` |
| a file the app also writes itself | `bash "$SKILL/scripts/traps.sh" "app's own"` |
| shell, bashio, a symlinked script | `bash "$SKILL/scripts/traps.sh" bashio` |
| `Dockerfile`, `build.json`, an arch guard | `bash "$SKILL/scripts/traps.sh" dockerfile` |
| Chromium, Electron, Xvfb | `bash "$SKILL/scripts/traps.sh" chromium` |
Then validate with `scripts/validate.sh <addon> --vs-master`, and write behavioural tests for
anything with branches, targeting **the regression a reviewer described**, not just the happy
path.
## 5. Simplify
Before requesting review, check: did the diff stay at the ladder level chosen in step 3? Can this
be solved by deleting instead of adding? Is the fix bigger than what it fixes? How does it fail in
three years? And on reuse: does any hunk reimplement something `.templates/`, another script in
this add-on, or a sibling add-on already does — and if a future add-on hits this same problem,
will it find one way to solve it or two? Fold a near-duplicate into the existing mechanism, or
justify the divergence in the PR body — but never at the cost of an isolation rule
`references/traps.md` documents: scripts shared by symlink with the webtop add-ons take a new
numbered script, not an edit. Case studies of what happens when this check is skipped:
`references/simplify.md`.
Six questions over your own diff, before anyone else reads it:
- **Level** — did the diff stay at the ladder level chosen in step 3, or creep up one?
- **Deletion** — can this be solved by deleting instead of adding?
- **Size** — is the fix bigger than the thing it fixes?
- **Reuse** — does any hunk reimplement what `.templates/`, another script in this add-on, or a
sibling add-on already does? If a future add-on hits this problem, will it find one way to solve
it or two? Fold near-duplicates in, or justify the divergence in the PR body.
- **Depth** — is this a special case bolted onto shared infrastructure? Fix the shared mechanism
instead once more than one add-on hits it; generalising from a single case is how bespoke
designs get built, so below that bar the special case is the right call.
- **Longevity** — how does this fail in three years, when the base image or upstream has moved?
The standing exception to Reuse and Depth: scripts shared by symlink with the webtop add-ons take a
new numbered script, never an edit (`references/traps.md#shell-and-bashio`). Case studies for the rest, including
what shipped when this pass was skipped: `references/simplify.md`.
## 6. Codex attacks the code, then simplify what the review added (full loop only)
Same delegated invocation, pointed at `git diff origin/master...HEAD` plus your reasoning per
hunk. Details in `references/codex-review.md`.
Then run step 5's checks again over the hunks the review changed. Adversarial review only ever
argues *for* another branch — that is its job — so accepting objections ratchets the diff upward,
and nothing else in the loop walks it back down. For each accepted objection: is the case it
defends one you have now demonstrated, or one you have merely been told about? Taking a
Then run step 5's checks again over the hunks the review changed. Adversarial review mostly argues
*for* another branch — that is what it is asked to do — so accepting objections tends to ratchet
the diff upward, and nothing else in the loop walks it back down. Sort each objection before you
write anything:
**"this is wrong"** is a bug and you fix it; **"this is undefended"** is a claim about some host,
and it needs the same demonstration you would demand of a measurement — is the case it defends one
you have now demonstrated, or one you have merely been told about? Taking a
correctness objection often deletes the code that made it necessary, and a fix that collapses back
to fewer lines than you started the review with is the normal outcome, not a suspicious one.
@@ -165,20 +183,23 @@ kind of edit that leaves a stray `fi` behind.
## 7. Open the PR
CI gates on a PR: **`CHANGELOG.md` updated** (hard fail), the **HA add-on linter**
(`frenck/action-addon-linter`, blocking — not the weekly Super-Linter, which is non-blocking), and
the **add-on image build**. Bump `version` anyway (`X.Y.Z.N`, never `X.Y.Z-N`, see
`references/traps.md#versioning`) — Supervisor won't offer a rebuild without it. Update
`README.md` if you added options; write the CHANGELOG heading as `## <version> (<date>)`,
matching the date format already in that file — almost always ISO `YYYY-MM-DD`, see
`references/traps.md#ci-and-review-bots`.
Three hard gates — **`CHANGELOG.md` updated**, the **HA add-on linter**
(`frenck/action-addon-linter`), and the **add-on image build** — but only on a PR that changes a
top-level `config.*`. On a PR that doesn't (docs, `.github/`, `.claude/`) they *skip*, which is not
the same as passing. Super-Linter runs on every PR and is `continue-on-error`, so it never blocks;
fix its real findings anyway. Nothing checks the version bump, so bump it yourself — Supervisor
won't offer a rebuild without one, and `CLAUDE.md` has the format. Update `README.md` if you added
options; write the CHANGELOG heading as `## <version> (<date>)`, matching the date format already
in that file — almost always ISO `YYYY-MM-DD`, see `references/traps.md#ci-and-review-bots`.
Write the body to a file, `gh pr create --body-file`: state what was measured, what changed,
**what is not verified**, and how to roll back the riskiest hunk alone.
## 8. Resolve review comments
`scripts/pr_review.sh list|reply|resolve|status|watch <PR>`. For every comment, **reproduce the
`scripts/pr_review.sh list|status|watch <PR>` to read, `reply <PR> <COMMENT_ID> <text|@file>` and
`resolve <PR> <THREAD_ID…|--all>` to answer; run it with no arguments for the full usage. For every
comment, **reproduce the
claim before agreeing or disagreeing** — reviewers are frequently right and occasionally
confidently wrong; a reproduction takes a minute and decides it either way. Reply with the
evidence, then resolve. **Push back when you're right**, on the thread — a resolved-but-wrong
@@ -192,13 +213,12 @@ work" — either it was exercised, or say plainly it wasn't.
Light path: verification is `validate.sh` plus CI; anything beyond that is Assumed. Full loop: CI
passing proves the build works, not that the change does anything — re-run the measurement that
motivated the work once the rebuilt add-on is running. After merge, `git fetch origin master`
(the tracking ref is stale otherwise), then confirm the *changes* survived — `git diff
origin/master -- <the paths you touched>` comes back empty. Ancestry is not the check: a revert
leaves your commit in history and undoes its tree, so `--contains` reports success either way. The
builder's revert-on-failure job can revert a merge for reasons unrelated to your diff (see
`references/traps.md#ci-and-review-bots`). Real "merged and inert" examples, and what
to do when a fix can't be self-verified: `references/evidence.md`.
motivated the work once the rebuilt add-on is running. Real "merged and inert" examples, and what
to do when a fix cannot be self-verified: `references/evidence.md`. Then confirm the change
survived the merge: `git fetch origin master` first (the tracking ref is stale otherwise), then
`git diff origin/master -- <the paths you touched>` must come back empty. Ancestry is not the
check, and the builder reverts merges for reasons unrelated to your diff — both explained in
`references/traps.md#ci-and-review-bots`.
## 10. Calibrate and report

View File

@@ -2,9 +2,7 @@
Used for step 3 (plan review) and step 6 (code review), full loop only. Codex is a genuinely
different model reading the files itself; on this workload it has repeatedly been worth the
minutes. Delegate the invocation to a subagent (see SKILL.md's subagent-delegation note) so its
output doesn't land verbatim in your context — have the subagent return only Codex's objections
and your assessment of each.
minutes. Run it through a subagent, per SKILL.md's delegation note.
## Invocation
@@ -12,11 +10,12 @@ and your assessment of each.
on ~4 KB prompts (2026-08-03); the CLI with the same content succeeded. The MCP tool is still fine
for short questions.
`--sandbox read-only` lets Codex read files but blocks writes and command execution, and
`approval_policy=never` means it will not be prompted for permission to run anything either — so
paste every number into the prompt rather than expecting Codex to gather it. `- <` feeds the
prompt file on stdin. Run it in the background so you are not blocked for the several minutes it
takes (`&` here, or your harness's background-task mechanism):
`--sandbox read-only` blocks writes, not reads, and `approval_policy=never` stops it asking for
permission rather than stopping it acting — so it can still run read-only commands and often
falls back to fetching the repo from GitHub instead of reading your worktree. Paste every number
into the prompt rather than expecting it to gather them, and treat what it reports about *local*
state as unverified. `- <` feeds the prompt file on stdin. Run it in the background so you are not
blocked for the several minutes it takes (`&` here, or your harness's background-task mechanism):
```bash
codex exec --model gpt-5.6-sol --sandbox read-only --skip-git-repo-check \
@@ -40,9 +39,5 @@ codex exec --model gpt-5.6-sol --sandbox read-only --skip-git-repo-check \
confidently, and separately caught a genuine methodology error in the same review. Treat its
confirmations with the same scepticism as its objections — especially about the build.
**Its objections ratchet complexity upward.** An adversarial reviewer is asked to find what could
go wrong, so its output is a list of arguments for more code; it is never asked whether the branch
it wants is reachable. Separate "this is wrong" from "this is undefended" before you write
anything: the first is a bug and you fix it, the second is a claim about some host, and it needs
the same demonstration you would demand of a measurement. That is what step 6's second simplify
pass is for.
**Its objections only ever argue for more code** — it is asked what could go wrong, never whether
the branch it wants is reachable. Sort them before writing anything; SKILL.md step 6 is that pass.

View File

@@ -1,16 +1,24 @@
# Evidence — measurement methodology and case studies
## Why summed RSS and reserved-vs-resident both matter
## How to read the numbers
- **Summed RSS double-counts shared pages.** Removing a duplicate process frees its *private*
memory, not its RSS. `scripts/measure.sh` reports PSS and private alongside RSS — quote
**private** when arguing "removing this saves N MB".
- **A big mapping is not necessarily resident.** Large SysV/tmpfs segments are lazily populated;
reserved size is reported separately from resident for this reason.
- `/proc/meminfo` and `free` show **host** figures (no memory cgroup namespace here) — never
attribute those to the add-on.
- Sample duration matters: a 3 s CPU sample measured 2.3% where a 20 s sample measured 21.6% for
the same process. Use ≥20 s for anything you report.
**Summed RSS overstates savings.** Shared library pages are counted once per process, so removing
a duplicate frees its *private* memory, not its RSS. Measured example: four MCP shims summed to
882 MB RSS but 643 MB PSS / 564 MB private, and per-process private ranged 54 MB down to 2 MB
which completely changes which duplicate is worth removing. Quote private when arguing "removing
this saves N MB".
**A large mapping is often not resident.** SysV/tmpfs segments are lazily populated. Xvfb's
506 MB framebuffer shows `Rss: 0` in `/proc/<pid>/smaps`. Check before calling anything a leak.
**`/proc/meminfo` and `free` show host figures** — there is no memory cgroup namespace here.
Never attribute those totals to the add-on.
**A short CPU sample is not a CPU measurement.** A 3 s sample measured 2.3% where a 20 s sample
measured 21.6% for the same process. Use >= 20 s for anything you report.
`scripts/measure.sh` already reports PSS and private alongside RSS, and resident separately from
reserved, so these three only bite when you compute a figure yourself or quote one from `ps`.
## Before asserting anything, ask what would show it false

View File

@@ -30,28 +30,24 @@ Being able to build the complicated thing is not a reason to.
It took the maintainer asking "is this the simplest way possible?" to run the pass that step 6
now requires.
## Checks worth running against your own diff
## Where SKILL.md step 5's questions get hard
- **Did the diff stay at the ladder level chosen in step 3?** If it crept up a level, either
justify that out loud or redo it at the level you chose.
- **Can this be solved by deleting instead of adding?** A flag that shouldn't be passed, a
process that shouldn't start, a registration that shouldn't be duplicated. Deleting usually
shrinks the regression surface — but not always: the `/dev/shm` case in
`references/evidence.md` is a removal that reintroduced a crash loop on hosts unlike this one.
A removal that depends on a host default still needs the same verification as an addition.
- **Is the fix bigger than the thing it fixes?** That is a smell, not a rule — but it usually
means the problem was framed one level too deep.
- **For each defensive branch: what input reaches it, on which image or host?** Go and check,
the way you would check a measurement. The bar is being able to **name** the case, not to
reproduce it here: Docker's 64 MB `/dev/shm` default is documented behaviour that HA does not
override, so the bullet above keeps that guard even though this host measured 7.7 GB. Nobody
could name a single image shipping `with-contenv` without `s6-dumpenv`, so that fallback went.
If you cannot name the case, delete the branch — the situation then fails the way it already
fails today, visibly, instead of through a second path that is never exercised and silently
rots as the base images move. Weigh the cost too: a one-flag guard against a crash you cannot
rule out is cheap, a second code path that degrades to the pre-fix behaviour anyway is not.
Write down in the PR body what you cut and why, so the next person does not re-add it from the
same reasoning.
- **How does this fail in three years**, when the base image, Electron, or upstream has moved?
Code that reads a documented knob keeps working. Code that reaches into private internals
does not.
**Deletion is not automatically the safe direction.** The `/dev/shm` case in `evidence.md` is a
removal that reintroduced a crash loop on hosts unlike this one. A removal that depends on a host
default needs the same verification as an addition.
**Naming the case is the bar for a defensive branch, not reproducing it.** Docker's 64 MB
`/dev/shm` default is documented behaviour that Home Assistant does not override, so that guard
stays even though this host measured 7.7 GB. Nobody could name a single image shipping
`with-contenv` without `s6-dumpenv`, so that fallback went. If you cannot name the case, delete the
branch: the situation then fails the way it already fails today, visibly, instead of through a
second path that is never exercised and silently rots as the base images move. Weigh the cost both
ways — a one-flag guard against a crash you cannot rule out is cheap; a second code path that
degrades to the pre-fix behaviour anyway is not. Write in the PR body what you cut and why, so the
next person does not re-add it from the same reasoning.
**"Bigger than the thing it fixes" is a smell, not a rule** — but it usually means the problem was
framed one level too deep.
**Three-year failure** favours code that reads a documented knob. Code that reaches into private
internals does not survive the base image moving.

View File

@@ -9,7 +9,6 @@ workflows and lint rules — that is not repeated here.
## Contents
- [Environment and workspace](#environment-and-workspace)
- [Measurement](#measurement)
- [Passing values into base-image services](#passing-values-into-base-image-services)
- [Writing into an app's own config](#writing-into-an-apps-own-config)
- [Shell and bashio](#shell-and-bashio)
@@ -46,20 +45,6 @@ gate. One observed run took ~3 hours, with 20+ runs queued against 2 executing
runner contention, not the diff. Check `gh run list` before concluding your PR is stuck. Poll in
a background task, and never claim the build is verified when it hasn't run.
## Measurement
**Summed RSS overstates savings.** Shared library pages are counted once per process, so removing
a duplicate frees its *private* memory, not its RSS. Measured example: four MCP shims summed to
882 MB RSS but 643 MB PSS / 564 MB private, and per-process private ranged 54 MB down to 2 MB —
which completely changes which duplicate is worth removing. Quote private when arguing "removing
this saves N MB".
**A large mapping is often not resident.** SysV/tmpfs segments are lazily populated. Xvfb's
506 MB framebuffer shows `Rss: 0` in `/proc/<pid>/smaps`. Check before calling anything a leak.
**`/proc/meminfo` and `free` show host figures** — there is no memory cgroup namespace here.
Never attribute those totals to the add-on.
**`rtk` filters some command output.** For a complete listing, redirect to a file and read that
(`ps ... > $SP/ps.txt`), or use `rtk proxy <cmd>`.
@@ -85,10 +70,28 @@ service run scripts including `svc-xorg`, and Xvfb runs with `-vfbdevice /dev/dr
- `00-global_var.sh` is cont-init **00**. Any cont-init script numbered higher runs *after* the
injection, so it cannot change what a service will see through stage 2.
- LSIO's `svc-xorg` starts `#!/usr/bin/env bashio`, **not** `with-contenv`, so it never reads
stage 3 at all. Writing `container_environment` for it is a silent no-op — that shipped: the
file was written 6 seconds before Xvfb started, and Xvfb still came up at the base-image
default.
- **Whether a service's own shebang still decides stage 3 depends on whether `ha_entrypoint.sh`
runs as PID 1** — i.e. whether the add-on replaces the base `ENTRYPOINT ["/init"]` with one that
makes `ha_entrypoint.sh` itself the container's entrypoint (live today in `ente` and
`free_games_claimer`; `wger` has the override written into its Dockerfile but commented out, so
it is not currently one of these — check the Dockerfile, not this list). Under the normal
`/init` path this script runs as the stage-2 hook, `$PID1` is false, and it never touches
`services.d/*/run` or `s6-overlay/s6-rc.d/*/run` at all (`.templates/ha_entrypoint.sh:430`,
gated on `if $PID1`) — s6's own stage 1 already created `/run/s6/container_environment` before
any cont-init script ran, so a service's shipped `with-contenv` shebang reads it normally.
Only the `ENTRYPOINT`-override path breaks this, and it breaks it twice over: s6 stage 1 never
runs, so nothing ever creates the envdir; and because `ha_entrypoint.sh` is now PID 1, it
rewrites the first line of every service `run` file to whichever shebang its own
`candidate_shebangs` probe landed on. That probe's first candidate,
`/command/with-contenv bashio`, fails precisely because the envdir was never created, so it
falls through to `/usr/bin/env bashio` for every service — the real mechanism behind the shipped
`svc-xorg` failure (its envdir file was written 6 seconds before Xvfb started, and Xvfb still
came up at the base-image default). `cont-init.d` scripts are a separate case: `run_one_script`
rewrites their shebang unconditionally, with no `$PID1` gate, so a cont-init script's own
shebang is never informative either way. `ha_entrypoint.sh` dumps the environment itself to
compensate for the missing envdir, and the envdir writes in `00-global_var.sh` /
`01-config_yaml.sh` are `if [ -d ]` guarded, so they take effect only once something has created
that directory.
**Renaming an option to match a base-image env var moves validation out of your script and into
the schema.** `00-global_var.sh` exports empty strings (only objects/arrays/nulls are dropped),
@@ -190,11 +193,9 @@ build.
## Versioning
**`X.Y.Z.N`, never `X.Y.Z-N`.** A hyphen parses as a semver pre-release, which Supervisor treats
as *older* than `X.Y.Z` — the update is never offered.
Date-based versions (`2026.08.03`) are common here. Check whether master has already moved to the
version you were about to use.
`CLAUDE.md` owns the format (`X.Y.Z.N`, never `X.Y.Z-N`, and why). The one thing it does not say:
date-based versions (`2026.08.03`) are common here, so check whether master has already moved to
the version you were about to use before you pick it.
## Chromium / Electron under Xvfb
@@ -254,13 +255,38 @@ without your involvement — another reason a shared checkout goes stale mid-tas
**Reviewers**: CodeRabbit (deepest — often runs scripts to prove a claim; reviews ~9 minutes
after the PR opens, or on `@coderabbitai review`), chatgpt-codex-connector, Copilot, Codacy.
**Codacy `action_required` is this repo's normal state.** Other open PRs show the same. It
exposes no annotations via the API, so its findings are only visible in the maintainer's Codacy
account. Note it and move on rather than guessing.
**Codacy is red on essentially every add-on PR and gates nothing.** `gh pr checks` reports it as
`fail` (older runs showed `action_required`); #3019, #3044 and #3050 all merged with it failing,
and `master` carries no branch protection, so no check is required in the GitHub sense. It exposes
no annotations via the API, so its findings are only visible in the maintainer's Codacy account.
Note it and move on rather than guessing. `pr_review.sh watch` therefore prints it every poll but
keeps it out of the verdict — the one check on that list, which is a denylist of known noise, not
an allowlist of gates, so a job added to CI later counts as blocking until someone exempts it.
**Resolving a review thread requires GraphQL** (`resolveReviewThread`); the REST API cannot do it.
`scripts/pr_review.sh` wraps fetch / reply / resolve.
**`gh pr checks` output is TAB-separated, and every blocking gate here has spaces in its name.**
Parsing it with awk's default field splitting truncates each check to its first word and reads the
wrong column as the state: `Codacy Static Code Analysis<TAB>fail` becomes `Codacy=Static`, and
`Test addon build (wger)<TAB>pending` becomes `Test=addon`. A `case` over that string then matches
neither `*fail*` nor `*pending*` and falls through to the "all passing" branch — the failure mode
that makes a CI-reporting command lie. `pr_review.sh watch` called #3044 green while Codacy was
red, and on #3042 printed "settled — all passing" while the HA add-on linter was failing; it would
also have called a build that had not started a pass. Use `awk -F'\t'`, judge the state column
alone (never the joined `name=state` text, or a check named `flaky-fail-detector` reads as a
failure), and treat an unrecognised state as a failure instead of letting it reach the passing
branch. Fixed in #3052.
The TSV is gh's *non-TTY* renderer, which is what `$(gh pr checks ... | awk)` always gets; attached
to a terminal the same command prints a coloured, aligned table with a summary line, so never
sanity-check the format by eye in a shell and assume the script sees that. `gh pr checks --json`
would be sturdier, and Copilot recommends it (#3052), but it does not exist before gh 2.36 and the
add-on ships 2.23 — it fails with `unknown flag: --json`. The parse is therefore built to fail
safe instead: states are allowlisted, so a header row would land in the failure branch and a
space-aligned table would parse to zero rows and keep `watch` waiting. Either way it cannot
return a false pass.
**CHANGELOG heading dates are ISO, whatever the bots' defaults say.** Match the format already in
the add-on's file. Repo-wide that is `## <version> (YYYY-MM-DD)`: 7705 dated headings against 363
in `DD-MM-YYYY`, and the newest entry is ISO in 125 of 135 add-ons. Copilot flags an ISO file that

View File

@@ -8,6 +8,10 @@
# pr_review.sh resolve <PR> <THREAD_ID...|--all> --all = every unresolved, asks first
# pr_review.sh watch <PR> [minutes] poll checks (run this backgrounded)
#
# watch exits 0 when every blocking check passed *or was skipped* — a PR touching no add-on
# skips all three gates, and it says so — 1 on failure, 2 if it ran out of minutes. Codacy is
# advisory here: printed every poll, excluded from the verdict.
#
# Reviewers seen here: coderabbitai (deepest; reviews ~9 min after open, or on
# "@coderabbitai review"), chatgpt-codex-connector, Copilot, Codacy.
#
@@ -21,7 +25,7 @@ REPO="${HASSIO_REPO:-}"
[ -z "$REPO" ] && { echo "cannot determine repo; set HASSIO_REPO=owner/name" >&2; exit 1; }
echo "repo: $REPO" >&2
CMD="${1:-}"; PR="${2:-}"
[ -z "$CMD" ] || [ -z "$PR" ] && { sed -n '2,16p' "$0" | sed 's/^# \?//'; exit 1; }
[ -z "$CMD" ] || [ -z "$PR" ] && { sed -n '2,20p' "$0" | sed 's/^# \?//'; exit 1; }
case "$CMD" in
list)
@@ -90,25 +94,72 @@ resolve)
;;
watch)
MINS="${3:-180}" # the addon build alone has taken ~3h; 20 was far too short
# Checks that are red on essentially every add-on PR here and gate nothing: master carries no
# branch protection, and #3019, #3044 and #3050 all merged with Codacy failing. They are kept
# out of the verdict but always printed, so the reader still sees them and can judge. This is
# deliberately a denylist of known noise, not an allowlist of blocking checks — a job added to
# CI later counts as blocking until someone puts it here on purpose.
ADVISORY_CHECKS="Codacy Static Code Analysis" # one per line if more are ever added
wfail=2 # not 0: running out of minutes with checks still pending is not a pass
c=""; bstates=""; adv=""
for i in $(seq 1 "$MINS"); do
c=$(gh pr checks "$PR" 2> /dev/null | awk '{print $1"="$2}' | tr '\n' ' ')
if [ -z "$c" ]; then
# gh pr checks emits TAB-separated columns with no header when its output is not a TTY,
# which inside this $(... | awk) it never is. (Attached to a terminal it prints a wholly
# different ANSI table; --json would be sturdier still but does not exist before gh 2.36,
# and 2.23 ships here.) Every blocking gate has spaces in its name — "Addon linting
# (wger)", "Test addon build (wger)" — so awk's default separator split them on
# whitespace: "Codacy Static Code Analysis<TAB>fail" became "Codacy=Static" and the state
# column was never read at all. watch printed "all passing" on a red #3044 and on #3042
# with the linter failing, and could not see a pending build either.
# If that format ever does change, the allowlist below fails safe rather than passing: a
# header row lands in the failure branch, and a space-aligned table parses to no rows,
# which keeps watch waiting instead of returning 0.
rows=$(gh pr checks "$PR" 2> /dev/null | awk -F'\t' -v ADV="$ADVISORY_CHECKS" '
BEGIN { n = split(ADV, a, "\n"); for (j = 1; j <= n; j++) adv[a[j]] = 1 }
NF >= 2 { print (($1 in adv) ? "A" : "B") "\t" $1 "=" $2 "\t" $2 }')
if [ -z "$rows" ]; then
# Normal in the first minutes after `gh pr create`, and also whenever gh errors.
# Calling that "settled" would report success for checks that never ran.
echo "[$i] no checks reported yet (gh returned nothing) — still waiting"
sleep 60; continue
fi
c=$(printf '%s\n' "$rows" | cut -f2 | tr '\n' ' ')
echo "[$i] $c"
case "$c" in
*pending*) sleep 60 ;;
*fail* | *error* | *cancel*) echo "settled — with FAILURES (see above)"; wfail=1; break ;;
*) echo "settled — all passing"; wfail=0; break ;;
esac
# Judge the state column only, never the joined name=state line: a check whose NAME
# contains "fail" must not read as a failure.
bstates=$(printf '%s\n' "$rows" | awk -F'\t' '$1 == "B" { print $3 }' | tr '\n' ' ')
adv=$(printf '%s\n' "$rows" | awk -F'\t' '$1 == "A" { print $2 }' | tr '\n' ' ')
if [ -z "$bstates" ]; then
echo " only advisory checks have reported — no blocking check has run yet"
sleep 60; continue
fi
# Allowlist the good states rather than denylisting the bad ones: an unrecognised state
# must land in the failure branch, because falling through to "passing" is this command's
# worst outcome.
nbad=0; npend=0
for s in $bstates; do
case "$s" in
pass | skipping) ;;
pending) npend=$((npend + 1)) ;;
*) nbad=$((nbad + 1)) ;;
esac
done
if [ "$nbad" -gt 0 ]; then
echo "settled — blocking checks FAILED:"
printf '%s\n' "$rows" |
awk -F'\t' '$1 == "B" && $3 != "pass" && $3 != "skipping" && $3 != "pending" { print " " $2 }'
wfail=1; break
elif [ "$npend" -gt 0 ]; then
sleep 60; continue
else
echo "settled — blocking checks passing"; wfail=0; break
fi
done
[ "$wfail" -eq 2 ] && echo "gave up after ${MINS}m, checks still unsettled — NOT a pass"
# Printed on pass and on failure alike: it is excluded from the verdict, not hidden.
[ -n "$adv" ] && echo " advisory (non-blocking, not counted in the verdict): $adv"
# A PR touching no */config.* skips the CHANGELOG, linter and build jobs outright (#3018).
case "${c:-}" in *skipping*) echo " ...of which some were SKIPPED — a skipped job tested nothing" ;; esac
case " $bstates " in *" skipping "*) echo " ...of which some were SKIPPED — a skipped job tested nothing" ;; esac
echo "note: long queues here are usually account runner contention, not your diff."
exit "$wfail"
;;

View File

@@ -5,7 +5,14 @@
# Usage: preflight.sh [repo-path] [addon-slug]
set -uo pipefail
REPO="${1:-/data/claude/hassio-addons}"
# Default to the checkout this is run from, never a fixed path: a fixed path silently inspected
# the main checkout while the caller worked in a worktree, reporting a branch nobody was editing —
# the exact stale-checkout trap this script exists to catch. Falling back to one when git cannot
# answer would recreate it, so refuse instead and make the caller say which repo they mean.
REPO="${1:-}"
if [ -z "$REPO" ]; then
REPO=$(git rev-parse --show-toplevel 2> /dev/null) || REPO=""
fi
SLUG="${2:-}"
echo "== tools =="
@@ -26,9 +33,14 @@ fi
echo
echo "== repo =="
# git-aware check: in a worktree .git is a file, not a directory
if [ -z "$REPO" ]; then
echo " not inside a git checkout, and no repo path given"
echo " -> pass one explicitly: preflight.sh <repo-path> [addon-slug]"
exit 1
fi
if ! git -C "$REPO" rev-parse --git-dir > /dev/null 2>&1; then
echo " no git repo at $REPO"
exit 0
exit 1
fi
cd "$REPO" || exit 0
branch=$(git branch --show-current 2> /dev/null || echo "(detached)")

View File

@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Print one section of references/traps.md.
#
# traps.md is ~18 KB and every section but one is irrelevant to any given edit: a shell fix needs
# 642 bytes of it, a Dockerfile fix 2.6 KB, and "CI and review bots" — 35% of the file — is needed
# at steps 7-8 and never at step 4. A markdown anchor cannot be loaded on its own, so reading the
# file to reach one section pays for all of them. This prints just the section, so the routing
# table in SKILL.md step 4 costs what it claims to.
#
# Usage: traps.sh <keyword> # substring of a section heading, case-insensitive
# traps.sh # list the sections
set -uo pipefail
TRAPS="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/references/traps.md"
[ -f "$TRAPS" ] || { echo "not found: $TRAPS" >&2; exit 1; }
# The Contents list duplicates the headings; grep the headings themselves so the list cannot drift.
list() {
echo "sections (pass any substring):"
grep '^## ' "$TRAPS" | grep -v '^## Contents' | sed 's/^## / /'
}
[ $# -eq 0 ] && { list; exit 0; }
# awk over exact heading text: section names contain '/' and other characters that would need
# escaping in a sed address, and a keyword matching several headings should be an error, not a
# silent pick of the first.
mapfile -t matches < <(grep '^## ' "$TRAPS" | grep -v '^## Contents' |
grep -iF -- "$1" | sed 's/^## //')
case "${#matches[@]}" in
0)
echo "no section matching '$1'" >&2
list >&2
exit 1
;;
1) ;;
*)
echo "'$1' matches ${#matches[@]} sections — be more specific:" >&2
printf ' %s\n' "${matches[@]}" >&2
exit 1
;;
esac
awk -v want="## ${matches[0]}" '
$0 == want { inside = 1; print; next }
inside && /^## / { exit }
inside { print }
' "$TRAPS"

View File

@@ -418,6 +418,7 @@ KuchenKavalier,,2026-08-10
KuerbisK,,2026-08-10
Kvasenok,,2026-08-10
KyleGolfer,,2026-08-10
L00PERY,,2026-09-06
L0rdShrek,Germany,
L1nKinc,Germany,
LaLaBer,,2026-08-10
@@ -444,6 +445,7 @@ LoginByCall,,2026-08-10
Lolekpolek,,2026-08-10
LonelySoul7X,,2026-08-10
Loong-He,China,2026-08-30
Loong-OvO,China,2026-09-06
Lorsel,Italy,
Luca2165801154,,2026-08-10
Lucius-Waverly,,2026-08-16
@@ -1390,6 +1392,7 @@ flostingapplesauce,,2026-08-10
flozi00,Germany,
flue17,,2026-08-10
fmcglinn,Australia,
foodstampou812,United States,2026-09-06
forming,,2026-08-10
forreggbor,Hungary,
foundbobby,United States,
@@ -1834,6 +1837,7 @@ m1kethai,Canada,
m23l,,2026-08-10
m2sh,"Iran, Islamic Republic of",
m4rcSA,,2026-08-10
m4rkolson,United States,2026-09-06
mProwler,United States,
mStrangers,,2026-08-10
mabt,,2026-08-10
@@ -2085,6 +2089,7 @@ p0wertiger,Poland,
paalwilliams,Germany,
pace6666,,2026-08-10
pafnow,France,
pahanitsch,Germany,2026-09-06
pandabreads,,2026-08-10
pankaj151,India,
papafrank66,,2026-08-10
@@ -2664,6 +2669,7 @@ yycsdm,,2026-08-10
yyll2233,,2026-08-10
z2833,,2026-08-10
z307917424,,2026-08-10
zacharee,Panama,2026-09-06
zachgilliam,United States,
zanyraspi,,2026-08-10
zdaar,,2026-08-10
1 username country last_checked
418 KuerbisK 2026-08-10
419 Kvasenok 2026-08-10
420 KyleGolfer 2026-08-10
421 L00PERY 2026-09-06
422 L0rdShrek Germany
423 L1nKinc Germany
424 LaLaBer 2026-08-10
445 Lolekpolek 2026-08-10
446 LonelySoul7X 2026-08-10
447 Loong-He China 2026-08-30
448 Loong-OvO China 2026-09-06
449 Lorsel Italy
450 Luca2165801154 2026-08-10
451 Lucius-Waverly 2026-08-16
1392 flozi00 Germany
1393 flue17 2026-08-10
1394 fmcglinn Australia
1395 foodstampou812 United States 2026-09-06
1396 forming 2026-08-10
1397 forreggbor Hungary
1398 foundbobby United States
1837 m23l 2026-08-10
1838 m2sh Iran, Islamic Republic of
1839 m4rcSA 2026-08-10
1840 m4rkolson United States 2026-09-06
1841 mProwler United States
1842 mStrangers 2026-08-10
1843 mabt 2026-08-10
2089 paalwilliams Germany
2090 pace6666 2026-08-10
2091 pafnow France
2092 pahanitsch Germany 2026-09-06
2093 pandabreads 2026-08-10
2094 pankaj151 India
2095 papafrank66 2026-08-10
2669 yyll2233 2026-08-10
2670 z2833 2026-08-10
2671 z307917424 2026-08-10
2672 zacharee Panama 2026-09-06
2673 zachgilliam United States
2674 zanyraspi 2026-08-10
2675 zdaar 2026-08-10

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 66 KiB

BIN
.github/stats.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

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

View File

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

View File

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

View File

@@ -166,7 +166,7 @@ jobs:
id: classify
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
continue-on-error: true
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Without this the action falls back to the OIDC -> Claude App token

View File

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

View File

@@ -19,7 +19,7 @@ slug="${slug#*_}"
if [ ! -f /config/configuration.yaml ] && [ ! -f /config/configuration.json ]; then
# New config location
CONFIGLOCATION="/config"
CONFIGFILEBROWSER="/addon_configs/${HOSTNAME/-/_}/config.yaml"
CONFIGFILEBROWSER="/app_configs/${HOSTNAME/-/_}/config.yaml"
else
# Legacy config location
CONFIGLOCATION="/config/addons_config/${slug}"

View File

@@ -174,7 +174,7 @@ slug="${slug#*_}"
# Check type of config folder
if [ ! -f /config/configuration.yaml ] && [ ! -f /config/configuration.json ]; then
CONFIGLOCATION="/config"
CONFIGFILEBROWSER="/addon_configs/${HOSTNAME/-/_}/$slug.sh"
CONFIGFILEBROWSER="/app_configs/${HOSTNAME/-/_}/$slug.sh"
else
CONFIGLOCATION="/config/addons_autoscripts"
CONFIGFILEBROWSER="/homeassistant/addons_autoscripts/$slug.sh"

View File

@@ -16,6 +16,54 @@ else
echo "Starting custom scripts"
fi
##########################################
# Install the stop handler #
##########################################
# As namespace PID 1 -- which is what "init: false" makes this script -- the kernel
# discards any signal whose handler is still SIG_DFL. Installed at the end of startup,
# as it used to be, the handler missed every stop that arrived while the Supervisor
# probe or the cont-init chain was still running: Home Assistant waited out the grace
# period for a SIGKILL and reported the add-on as failed. Nothing here is interrupted
# mid-write by the move -- only PID 1 is signalled, and bash runs a trap at a command
# boundary, so whatever external command is in flight reaps first.
terminate() {
local local_pid
# Best-effort, so errexit must not apply: this runs with `set -e` in force (validate_shebang
# leaves it on), and under errexit the first failing command aborts the handler and exits with
# its status, skipping the child-kill loop and the `exit 0` below. Every command in the body
# here is already guarded, but add-ons patch this function at build time -- postgres_15 and
# postgres_17 sed an unguarded `pg_ctl ... stop` in right after the echo -- and that command
# fails whenever the stop arrives before the database is up.
set +e
echo "Termination signal received, forwarding to subprocesses..."
if command -v pgrep >/dev/null 2>&1; then
while read -r pid; do
[ -n "$pid" ] || continue
echo "Terminating child PID $pid"
kill -TERM "$pid" 2>/dev/null || echo "Failed to terminate PID $pid"
done < <(pgrep -P "$$" || true)
else
for p in /proc/[0-9]*/; do
local_pid="${p#/proc/}"
local_pid="${local_pid%/}"
if [ "$local_pid" -ne 1 ] && grep -q "^PPid:[[:space:]]*$$" "/proc/$local_pid/status" 2>/dev/null; then
echo "Terminating child PID $local_pid"
kill -TERM "$local_pid" 2>/dev/null || echo "Failed to terminate PID $local_pid"
fi
done
fi
wait || true
echo "All subprocesses terminated. Exiting."
exit 0
}
# Only when this script is PID 1. Under Docker's own init the entrypoint is an ordinary
# child and the signal handling above belongs to that init, not to us.
if $PID1; then
trap terminate SIGTERM SIGINT
fi
##########################################
# Pick an exec-capable directory #
##########################################
@@ -428,31 +476,6 @@ if $PID1; then
echo " "
echo -e "\033[0;32mEverything started!\033[0m"
terminate() {
local local_pid
echo "Termination signal received, forwarding to subprocesses..."
if command -v pgrep >/dev/null 2>&1; then
while read -r pid; do
[ -n "$pid" ] || continue
echo "Terminating child PID $pid"
kill -TERM "$pid" 2>/dev/null || echo "Failed to terminate PID $pid"
done < <(pgrep -P "$$" || true)
else
for p in /proc/[0-9]*/; do
local_pid="${p#/proc/}"
local_pid="${local_pid%/}"
if [ "$local_pid" -ne 1 ] && grep -q "^PPid:[[:space:]]*$$" "/proc/$local_pid/status" 2>/dev/null; then
echo "Terminating child PID $local_pid"
kill -TERM "$local_pid" 2>/dev/null || echo "Failed to terminate PID $local_pid"
fi
done
fi
wait || true
echo "All subprocesses terminated. Exiting."
exit 0
}
trap terminate SIGTERM SIGINT
while :; do
sleep infinity &
wait $!

View File

@@ -70,7 +70,7 @@ version: "X.Y.Z" # upstream version (format varies; see Versioning sect
ingress: true/false
ingress_port: 8000
map:
- addon_config:rw # /addon_configs/<hostname>/
- app_config:rw # /app_configs/<hostname>/
- share:rw
- media:rw
- ssl

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 2026.08 (2026-08-01)
- Addon versions written in config.yaml now always comply with Home Assistant versioning: an upstream tag Home Assistant cannot order (`version-bf9e0b4f`, `ubuntu-2026-06-01`, ...) or would sort as older (`1.2.3-2`, `1.2.3+4`) no longer lands in config.yaml. The addon number is incremented instead, while the raw upstream tag stays in updater.json so the same release is never published twice
@@ -31,7 +32,7 @@
## 3.19
- New HA config logic implemented. Files are now located in the addon config file, that can be accessed from the addon_configs folder from my filebrowser or cloudcommander addons. Migration of data, custom configs, and custom scripts should be automatic. Please be sure to update all your links however ! For more information, see here : https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/
- New HA config logic implemented. Files are now located in the addon config file, that can be accessed from the app_configs folder from my filebrowser or cloudcommander addons. Migration of data, custom configs, and custom scripts should be automatic. Please be sure to update all your links however ! For more information, see here : https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/
- Feat : if there is no releases in a github repo, check if there is a valid package and get the highest tag https://github.com/alexbelgium/hassio-addons/issues/1168
- Feat : github_exclude applies to dockerhub

View File

@@ -115,7 +115,7 @@ verbose: "false"
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables through the `addon_config` mapping:
This addon supports custom scripts and environment variables through the `app_config` mapping:
- **Custom scripts**: See [Running Custom Scripts in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Running-custom-scripts-in-Addons)
- **Environment variables**: Use the add-on `env_vars` option and see [Add Environment Variables to your Addon](https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon) for details.

View File

@@ -8,7 +8,7 @@ environment:
image: ghcr.io/alexbelgium/addons_updater-{arch}
init: false
map:
- addon_config:rw
- app_config:rw
name: Repository Updater
options:
date_iso8601: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 2.8.0 (2026-09-05)
- Update to latest version from lklynet/aurral (changelog : https://github.com/lklynet/aurral/releases)

View File

@@ -21,7 +21,7 @@ ports_description:
webui: http://[HOST]:[PORT:3001]
map:
- addon_config:rw
- app_config:rw
- share:rw
- media:rw

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 0.12.1 (2026-09-03)
- Update to Baikal 0.12.1 from 0.10.1 (changelog : <https://github.com/sabre-io/Baikal/releases>). This includes the 0.12.1 fix for an XSS vulnerability that let an authenticated user take over the admin interface by renaming a calendar
@@ -28,7 +29,7 @@
## 0.9.5 (2024-04-27)
- Update to latest version from ckulka/baikal-docker (changelog : https://github.com/ckulka/baikal-docker/releases)
## 0.9.4-3 (2024-04-26)
- ⚠ MAJOR CHANGE : switch to the new config logic from homeassistant. Your configuration files will have migrated from /config/hassio_addons/baikal to a folder only accessible from my Filebrowser addon called /addon_configs/something-baikal. This avoids the addon to mess with your homeassistant configuration folder, and allows to backup the options. Migration of data, custom configs, and custom scripts should be automatic. Please be sure to update all your links however ! For more information, see here : https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/
- ⚠ MAJOR CHANGE : switch to the new config logic from homeassistant. Your configuration files will have migrated from /config/hassio_addons/baikal to a folder only accessible from my Filebrowser addon called /app_configs/something-baikal. This avoids the addon to mess with your homeassistant configuration folder, and allows to backup the options. Migration of data, custom configs, and custom scripts should be automatic. Please be sure to update all your links however ! For more information, see here : https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/
## 0.9.4-2 (2024-01-14)

View File

@@ -50,7 +50,7 @@ Configurations can be done through the app webUI, except for the following optio
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables through the `addon_config` mapping:
This addon supports custom scripts and environment variables through the `app_config` mapping:
- **Custom scripts**: See [Running Custom Scripts in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Running-custom-scripts-in-Addons)
- **Environment variables**: See [Add Environment Variables to your Addon](https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon)

View File

@@ -66,7 +66,7 @@ devices:
- /dev/nvme2
image: ghcr.io/alexbelgium/baikal-{arch}
map:
- addon_config:rw
- app_config:rw
- homeassistant_config:rw
- share:rw
- ssl:ro

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 2026.02.28 (28-02-2026)
- Minor bugs fixed
## 2026.02.25-2 (25-02-2026)

View File

@@ -86,7 +86,7 @@ This addon supports mounting both local drives and remote SMB shares:
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables through the `addon_config` mapping:
This addon supports custom scripts and environment variables through the `app_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.

View File

@@ -64,7 +64,7 @@ ingress: true
ingress_stream: true
init: false
map:
- addon_config:rw
- app_config:rw
- media:rw
- share:rw
- ssl

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 1.6.0.2 (2026-07-27)
@@ -26,7 +27,7 @@
- Update to latest version from linuxserver/docker-bazarr (changelog : https://github.com/linuxserver/docker-bazarr/releases)
## 1.5.4-1 (2026-01-08)
- ⚠ MAJOR CHANGE : switch to the new config logic from homeassistant. Your configuration files will have migrated from /config/addons_config/bazarr to a folder only accessible from my Filebrowser addon called /addon_configs/xxx-bazarr. This avoids the addon to mess with your homeassistant configuration folder, and allows to backup the options. Migration of data should be automatic. Please be sure to update all your links however ! For more information, see here : https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/
- ⚠ MAJOR CHANGE : switch to the new config logic from homeassistant. Your configuration files will have migrated from /config/addons_config/bazarr to a folder only accessible from my Filebrowser addon called /app_configs/xxx-bazarr. This avoids the addon to mess with your homeassistant configuration folder, and allows to backup the options. Migration of data should be automatic. Please be sure to update all your links however ! For more information, see here : https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/
## 1.5.4 (2026-01-08)
- Update to latest version from linuxserver/docker-bazarr (changelog : https://github.com/linuxserver/docker-bazarr/releases)

View File

@@ -76,7 +76,7 @@ ingress: true
ingress_entry: bazarr
init: false
map:
- addon_config:rw
- app_config:rw
- homeassistant_config:rw
- share:rw
- media:rw

View File

@@ -23,7 +23,7 @@ fi
slug=bazarr
if [ -d "/homeassistant/addons_config/$slug" ]; then
echo "Migrating /homeassistant/addons_config/$slug to /addon_configs/xxx-$slug"
echo "Migrating /homeassistant/addons_config/$slug to /app_configs/xxx-$slug"
cp -rnf /homeassistant/addons_config/"$slug"/. /config/ || true
mv /homeassistant/addons_config/"$slug" /homeassistant/addons_config/"$slug"_migrated
fi

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,3 +1,29 @@
## 20260910.2 (11-09-2026)
- Minor bugs fixed
## 20260910 (11-09-2026)
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 20260909.7 (10-09-2026)
- Correct the "first daily detection consensus" README section (review feedback on PR #3056): a merged-upstream setting stays in the build rather than disappearing, every attempt for a species is held back until one is accepted rather than only the first, and the "known to every active bird model" exemption is scoped per audio source, matching the implementation.
## 20260909.6 (10-09-2026)
- Document the fork-only "first daily detection consensus" setting (alexbelgium/birdnet-go#63): requires a second model to confirm each bird species' first detection of the day. Off by default; no behaviour change unless enabled.
## 20260909.5 (09-09-2026)
- Minor bugs fixed
## 20260909.4 (09-09-2026)
- Minor bugs fixed
## 20260909.3 (09-09-2026)
- Minor bugs fixed
## 20260909.2 (09-09-2026)
- Minor bugs fixed
## 20260909 (09-09-2026)
- Minor bugs fixed
## 20260908.2 (08-09-2026)
- Minor bugs fixed
## 20260908.1 (08-09-2026)
- Synced with upstream birdnet-go (4 commits); re-merges the open fork PRs, adding fork PR #62 (reanalyze a clip with every loaded model + one-click correction)
## 20260908 (08-09-2026)
- Synced with upstream birdnet-go (7 commits); re-merges the open fork PRs
## 20260907 (07-09-2026)
- Rebuild: re-merges the open fork PRs, picking up the updated fork PR #57
## 20260901.4 (01-09-2026)
- Minor bugs fixed
## 20260901.3 (01-09-2026)

View File

@@ -25,7 +25,7 @@ MQTT password : Ri5ahV1aipeiw0aelerooteixai5ohtoeNg6oo3mo0thi5te0phiezuge4Phoore
MQTT broker : tcp://core-mosquitto:1883
---
Edit this section of config.yaml found in addon_configs/db21ed7f_birdnet-go/:
Edit this section of config.yaml found in app_configs/db21ed7f_birdnet-go/:
mqtt:
enabled: true # true to enable MQTT
broker: tcp://core-mosquitto:1883 # MQTT (tcp://host:port)

View File

@@ -1,6 +1,6 @@
# Home assistant add-on: Birdnet-Go (from source)
> **⚠️ 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.
> **⚠️ 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, except the fork-only settings listed under [Fork-only settings](#fork-only-settings).
@@ -65,6 +65,38 @@ Additional variables can be configured using the config.yaml file found in /conf
- Config_env.yaml
Additional environment variables can be configured there
### Fork-only settings
These are currently fork-only settings from the [`alexbelgium/birdnet-go`](https://github.com/alexbelgium/birdnet-go) fork and are **not** in the standard add-on. [`merge-prs.sh`](./merge-prs.sh) syncs the fork's `main` with upstream before applying open PRs, so once a PR merges upstream the setting stays in this build — it just arrives via that sync instead of the PR-merge step, and stops being fork-only. Only a PR that is **closed without merging** drops its setting from later builds.
#### First daily detection consensus
Requires a second model to confirm the **first** detection of each bird species each day. Until one is accepted, every attempt for that species is held to the same two-model bar; only once a detection clears it does every later detection that day behave exactly as it does today, on a single model.
The first detection of a species in a day is the weakest evidence the pipeline produces, and it is the one that creates a "new species today" entry. Asking two models to agree on just that one detection removes most spurious new-species entries without slowing anything else down.
**Off by default.** Turn it on in the web UI under *Settings → Filters → First Daily Detection Consensus*, or in `config.yaml`:
```yaml
realtime:
firstdailyconsensus:
enabled: true
```
The setting is re-read on each detection cycle, so it takes effect without restarting the add-on.
It deliberately does **nothing** in these cases, all of which keep today's single-model behaviour:
- you run only one bird model (the default) — a second opinion does not exist, so the rule can never trigger
- the species is not a bird — bats and the non-bird sound classes Perch reports (insects, amphibians, mammals, `power_tool`, and so on)
- the species is not known to *every* active bird model analyzing that audio source — a species only one of them can name could never reach two confirmations. With several sources running different model combinations, this is decided per source, not add-on-wide
- a dynamic threshold has actually lowered the bar for that species, meaning you asked for a more permissive gate
- the taxonomy or the database cannot be consulted — it fails open and accepts the detection
In practice it only bites when a single audio source has two or more bird models (for example BirdNET plus Perch) analyzing it, on species all of them can identify. The trade is fewer false new-species entries, at the cost of occasionally delaying a genuine first sighting until a second model agrees.
Requires [alexbelgium/birdnet-go#63](https://github.com/alexbelgium/birdnet-go/pull/63).
### 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.
@@ -82,7 +114,7 @@ This addon supports mounting both local drives and remote SMB shares:
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables through the `addon_config` mapping:
This addon supports custom scripts and environment variables through the `app_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.

View File

@@ -80,7 +80,7 @@ ingress_entry: "ui/dashboard"
ingress_stream: true
init: false
map:
- addon_config:rw
- app_config:rw
- media:rw
- share:rw
name: Birdnet-go (customized and built from source)
@@ -127,5 +127,5 @@ slug: birdnet-go-dev
udev: true
url: https://github.com/alexbelgium/hassio-addons
usb: true
version: "20260901.4"
version: "20260910.2"
video: true

View File

@@ -11,6 +11,15 @@
# stamping) is written to the directory given as $1 so the Docker build can
# compile it.
#
# With --check the script does not build anything: it performs the exact same
# merge sequence, skips (instead of failing on) every conflicting PR, and prints
# one "!!! CONFLICT pr=#N conflicts-with=... files=... " line per offender before
# exiting 2. Use it to find conflicts *before* a build burns on them. This is a
# different question from GitHub's `mergeable` field, which compares a PR against
# its own base ref - for a stacked PR that base is another feature branch (often
# stale, sometimes belonging to a closed PR), so GitHub can report CLEAN for a PR
# that does not merge onto main at all.
#
# Environment:
# BIRDNET_FORK owner/repo of the fork (default alexbelgium/birdnet-go)
# BIRDNET_UPSTREAM owner/repo of the upstream (default tphakala/birdnet-go)
@@ -19,7 +28,26 @@
#
set -euo pipefail
TARGET_DIR="${1:?usage: merge-prs.sh <target-dir>}"
CHECK_ONLY="${MERGE_PRS_CHECK:-0}"
TARGET_DIR=""
while [ "$#" -gt 0 ]; do
case "$1" in
--check) CHECK_ONLY=1 ;;
-*) echo "unknown option: $1" >&2; exit 64 ;;
*)
# Last-one-wins would silently clone into the wrong directory if a caller ever
# appended an argument; the pre-flag script used "${1}", so refuse rather than
# quietly change which operand counts.
if [ -n "${TARGET_DIR}" ]; then
echo "usage: merge-prs.sh [--check] <target-dir>" >&2
exit 64
fi
TARGET_DIR="$1"
;;
esac
shift
done
: "${TARGET_DIR:?usage: merge-prs.sh [--check] <target-dir>}"
FORK="${BIRDNET_FORK:-alexbelgium/birdnet-go}"
UPSTREAM="${BIRDNET_UPSTREAM:-tphakala/birdnet-go}"
@@ -33,6 +61,52 @@ GH_TOKEN="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
log() { echo ">>> $*"; }
# Conflicting PRs collected in --check mode: "number|scope|files|title".
conflicting=()
LOCKFILE="frontend/package-lock.json"
# The single place that decides whether a conflicted merge is still acceptable.
# package-lock.json is generated content and stacked PRs can carry an older copy even when
# their source changes merge cleanly, so keep the lockfile already assembled on the base side
# — but only when it is the sole conflict. Any source conflict stays fatal.
# Returns 0 when it resolved and committed such a merge, 1 when the conflict is real.
# BOTH the real merge and the --check probe must go through here: when only the real merge
# applied the policy, the probe called a PR "conflicts-with=main" that the build would have
# merged fine, and printed the opposite remediation to the true one.
resolve_sole_lockfile() {
local dir="$1"
local -a conflicted
mapfile -t conflicted < <(git -C "${dir}" diff --name-only --diff-filter=U)
if [ "${#conflicted[@]}" -ne 1 ] || [ "${conflicted[0]}" != "${LOCKFILE}" ]; then
return 1
fi
log "Resolving generated ${LOCKFILE} conflict using the base tree"
git -C "${dir}" checkout --ours -- "${LOCKFILE}" || return 1
git -C "${dir}" add "${LOCKFILE}" || return 1
git -C "${dir}" commit --no-edit > /dev/null || return 1
}
# Does ${1} merge cleanly onto the pristine upstream-synced main? Probed in a
# throwaway worktree so the accumulated tree is left untouched. Tells apart a PR
# that is simply stale against main (fixable inside that PR's own branch) from
# one that only clashes with another open PR (needs a cross-PR decision).
merges_onto_main() {
local sha="$1" tmpdir probe rc=0
tmpdir="$(mktemp -d)"
probe="${tmpdir}/probe"
git worktree add --quiet --detach "${probe}" "${MAIN_SYNCED}"
if ! git -C "${probe}" merge --no-edit --no-ff -m probe "${sha}" > /dev/null 2>&1; then
# Same policy as the real merge, or this misclassifies a lockfile-only clash.
resolve_sole_lockfile "${probe}" > /dev/null 2>&1 || rc=1
fi
git worktree remove --force "${probe}" > /dev/null 2>&1 || true
# worktree remove only takes the child back; without this the mktemp parent is left behind
# on every checked conflict.
rmdir "${tmpdir}" > /dev/null 2>&1 || true
return "${rc}"
}
git config --global user.email "addon-builder@users.noreply.github.com"
git config --global user.name "BirdNET-Go Addon Builder"
git config --global advice.detachedHead false
@@ -47,6 +121,7 @@ git remote add upstream "${UPSTREAM_URL}"
git fetch --no-tags upstream main
# --no-ff keeps an explicit sync commit; a no-op when main is already current.
git merge --no-edit --no-ff upstream/main
MAIN_SYNCED="$(git rev-parse HEAD)"
log "Querying open non-draft PRs from ${FORK}"
auth_header=()
@@ -79,27 +154,43 @@ for entry in "${prs[@]}"; do
if ! git merge --no-edit --no-ff -m "Merge PR #${number}: ${title}" "${sha}"; then
mapfile -t conflicted_files < <(git diff --name-only --diff-filter=U)
# package-lock.json is generated content and stacked PRs can carry an
# older copy even when their source changes merge cleanly. Keep the
# lockfile already assembled from upstream and earlier PRs, but only
# when it is the sole conflict. Any source conflict remains fatal.
if [ "${#conflicted_files[@]}" -eq 1 ] \
&& [ "${conflicted_files[0]}" = "frontend/package-lock.json" ]; then
log "Resolving generated frontend/package-lock.json conflict using the accumulated tree"
git checkout --ours -- frontend/package-lock.json
git add frontend/package-lock.json
git commit --no-edit
if resolve_sole_lockfile .; then
: # generated lockfile only - the merge is committed and the build continues
else
echo "!!! Merge conflict while merging PR #${number} (${title})." >&2
if [ "${#conflicted_files[@]}" -gt 0 ]; then
printf '!!! Conflicting file: %s\n' "${conflicted_files[@]}" >&2
fi
echo "!!! Resolve the conflict in the fork or pause this PR, then rebuild." >&2
git merge --abort || true
if [ "${CHECK_ONLY}" = "1" ]; then
scope="accumulated"
merges_onto_main "${sha}" || scope="main"
conflicting+=("${number}|${scope}|${conflicted_files[*]:-}|${title}")
log "check mode: skipping PR #${number}, continuing with the rest"
continue
fi
echo "!!! Resolve the conflict in the fork or pause this PR, then rebuild." >&2
exit 1
fi
fi
done
if [ "${CHECK_ONLY}" = "1" ]; then
if [ "${#conflicting[@]}" -eq 0 ]; then
log "CHECK OK: every open non-draft PR merges into the combined build tree"
exit 0
fi
echo "!!! CHECK FAILED: ${#conflicting[@]} PR(s) would break the add-on build" >&2
for entry in "${conflicting[@]}"; do
IFS='|' read -r number scope files title <<<"${entry}"
echo "!!! CONFLICT pr=#${number} conflicts-with=${scope} files=${files} title=${title}" >&2
done
echo "!!! conflicts-with=main -> the PR is stale against main; merge main into its branch and resolve there." >&2
echo "!!! conflicts-with=accumulated -> the PR only clashes with another open PR; decide which one owns the hunk." >&2
exit 2
fi
log "Merged HEAD: $(git rev-parse --short HEAD)"
log "Source tree ready at ${TARGET_DIR}"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 20260827 (2026-08-29)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)

View File

@@ -25,7 +25,7 @@ MQTT password : Ri5ahV1aipeiw0aelerooteixai5ohtoeNg6oo3mo0thi5te0phiezuge4Phoore
MQTT broker : tcp://core-mosquitto:1883
---
Edit this section of config.yaml found in addon_configs/db21ed7f_birdnet-go/:
Edit this section of config.yaml found in app_configs/db21ed7f_birdnet-go/:
mqtt:
enabled: true # true to enable MQTT
broker: tcp://core-mosquitto:1883 # MQTT (tcp://host:port)

View File

@@ -79,7 +79,7 @@ This addon supports mounting both local drives and remote SMB shares:
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables through the `addon_config` mapping:
This addon supports custom scripts and environment variables through the `app_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.

View File

@@ -81,7 +81,7 @@ ingress_entry: "ui/dashboard"
ingress_stream: true
init: false
map:
- addon_config:rw
- app_config:rw
- media:rw
- share:rw
name: Birdnet-go

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 2026.08.15 (15-08-2026)
- Fix: `ALSA_CARD` now really selects the microphone. Its value was copied as-is into `REC_CARD`, but BirdNET-Pi hands `REC_CARD` to `arecord -D` / `ffmpeg -f alsa -i`, which expect an ALSA PCM name: a card index such as `1` gave `Unknown PCM 1` and no recording at all. It is now converted to `plughw:CARD=<value>,DEV=0`, while a value that already is a PCM name (`dsnoop:CARD=Audio,DEV=0`, `default`, `null`, `pulse`, `pipewire`, ...) is used as provided

View File

@@ -93,7 +93,7 @@ This addon supports mounting both local drives and remote SMB shares:
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables through the `addon_config` mapping:
This addon supports custom scripts and environment variables through the `app_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.

View File

@@ -61,7 +61,7 @@ image: ghcr.io/alexbelgium/birdnet-pi-zach-{arch}
ingress: true
init: false
map:
- addon_config:rw
- app_config:rw
- media:rw
- share:rw
- ssl

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 2026.08.15 (15-08-2026)
- Fix: `ALSA_CARD` now really selects the microphone. Its value was copied as-is into `REC_CARD`, but BirdNET-Pi hands `REC_CARD` to `arecord -D` / `ffmpeg -f alsa -i`, which expect an ALSA PCM name: a card index such as `1` gave `Unknown PCM 1` and no recording at all. It is now converted to `plughw:CARD=<value>,DEV=0`, while a value that already is a PCM name (`dsnoop:CARD=Audio,DEV=0`, `default`, `null`, `pulse`, `pipewire`, ...) is used as provided
@@ -156,7 +157,7 @@
- [ALL] Fix non-avx2 cpu support
## 2025.02.23 (2025-02-16)
- WARNING 2025.02.14/16 was buggy. If you installed it you need to restore a backup or delete manually your /addon_configs/xxx-birdnet-pi/birdnet.conf file and recreate it
- WARNING 2025.02.14/16 was buggy. If you installed it you need to restore a backup or delete manually your /app_configs/xxx-birdnet-pi/birdnet.conf file and recreate it
- Allow usage as a standalone container (thanks @gotschi) https://github.com/mcguirepr89/BirdNET-Pi/issues/211#issuecomment-2650095952
- Corrected a bug preventing to create db
- Corrected a bug to ensure the the most up-to-date birdnet.conf on fresh start

View File

@@ -93,7 +93,7 @@ This addon supports mounting both local drives and remote SMB shares:
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables through the `addon_config` mapping:
This addon supports custom scripts and environment variables through the `app_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.

View File

@@ -61,7 +61,7 @@ image: ghcr.io/alexbelgium/birdnet-pi-{arch}
ingress: true
init: false
map:
- addon_config:rw
- app_config:rw
- media:rw
- share:rw
- ssl

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 0.8.8 (2026-08-22)
- Update to latest version from Suncuss/BirdNET-PiPy (changelog : https://github.com/Suncuss/BirdNET-PiPy/releases)

View File

@@ -47,7 +47,7 @@ This add-on supports mounting both local drives and remote SMB shares:
### Custom Scripts and Environment Variables
This add-on supports custom scripts and environment variables through the `addon_config` mapping:
This add-on supports custom scripts and environment variables through the `app_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.

View File

@@ -67,7 +67,7 @@ ingress: true
ingress_stream: true
init: false
map:
- addon_config:rw
- app_config:rw
- ssl:rw
- share:rw
name: BirdNET-PiPy

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 1.94.121-ls127 (2026-09-05)
- Update to latest version from linuxserver/docker-brave (changelog : https://github.com/linuxserver/docker-brave/releases)

View File

@@ -26,7 +26,7 @@ image: ghcr.io/alexbelgium/brave-{arch}
ingress: true
init: false
map:
- addon_config:rw
- app_config:rw
- media:rw
- share:rw
- ssl

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 2026.09.05 (2026-09-05)
- Update to latest version from linuxserver/docker-chromium (changelog : https://github.com/linuxserver/docker-chromium/releases)

View File

@@ -26,7 +26,7 @@ image: ghcr.io/alexbelgium/chromium-{arch}
ingress: true
init: false
map:
- addon_config:rw
- app_config:rw
- media:rw
- share:rw
- ssl

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 2.56.3 (2026-09-05)
- Update to latest version from browserless/chrome (changelog : https://github.com/browserless/chrome/releases)

View File

@@ -50,7 +50,7 @@ TIMEOUT: 60000
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables through the `addon_config` mapping:
This addon supports custom scripts and environment variables through the `app_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.

View File

@@ -69,7 +69,7 @@ environment:
image: ghcr.io/alexbelgium/browserless_chrome-{arch}
init: false
map:
- addon_config:rw
- app_config:rw
name: Browserless Chromium
options:
env_vars: []

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 9.14.0 (2026-08-29)
- Update to latest version from linuxserver/docker-calibre (changelog : https://github.com/linuxserver/docker-calibre/releases)
@@ -223,7 +224,7 @@
- Update to latest version from linuxserver/docker-calibre
- BREAKING CHANGE : please relink your library according to https://github.com/alexbelgium/hassio-addons/issues/1154#issuecomment-1879182729
- ⚠ MAJOR CHANGE : switch to the new config logic from homeassistant. Your configuration files will have migrated from /config/hassio_addons/calibre to a folder only accessible from my Filebrowser addon called /addon_configs/something-calibre. This avoids the addon to mess with your homeassistant configuration folder, and allows to backup the options. Migration of data, custom configs, and custom scripts should be automatic. Please be sure to update all your links however ! For more information, see here : https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/ https://github.com/alexbelgium/hassio-addons/issues/1154
- ⚠ MAJOR CHANGE : switch to the new config logic from homeassistant. Your configuration files will have migrated from /config/hassio_addons/calibre to a folder only accessible from my Filebrowser addon called /app_configs/something-calibre. This avoids the addon to mess with your homeassistant configuration folder, and allows to backup the options. Migration of data, custom configs, and custom scripts should be automatic. Please be sure to update all your links however ! For more information, see here : https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/ https://github.com/alexbelgium/hassio-addons/issues/1154
## 7.2.0 (2023-12-16)

View File

@@ -98,7 +98,7 @@ This addon supports mounting both local drives and remote SMB shares:
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables through the `addon_config` mapping:
This addon supports custom scripts and environment variables through the `app_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.

View File

@@ -78,7 +78,7 @@ init: false
map:
- media:rw
- share:rw
- addon_config:rw
- app_config:rw
- homeassistant_config:rw
- ssl
name: Calibre

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 0.6.27.4 (2026-09-04)
- Fix: Kobo sync could not be enabled, failing with "Kepubify binary not found" even when the path was set by hand. The LinuxServer base image installs the converter as `/usr/bin/kepubify` with `curl -o`, which leaves it mode 0644 and gives it a name calibre-web does not accept : `binary_helper.py` only takes `kepubify-linux-64bit` or `kepubify-linux-32bit`, and only when `os.access(X_OK)` passes. The addon now makes the binary executable and publishes it as `/opt/kepubify/kepubify-linux-64bit`, the directory calibre-web's own autodetection already probes, so the path is filled in without any manual step (https://github.com/alexbelgium/hassio-addons/issues/3040)
@@ -28,7 +29,7 @@
- Update to latest version from linuxserver/docker-calibre-web (changelog : https://github.com/linuxserver/docker-calibre-web/releases)
## 0.6.24-10 (2025-05-28)
- Fix migration script
- Allow access to Calibre folder through /addon_configs/xxx-Calibre
- Allow access to Calibre folder through /app_configs/xxx-Calibre
## 0.6.24 (2024-11-23)
- Update to latest version from linuxserver/docker-calibre-web (changelog : https://github.com/linuxserver/docker-calibre-web/releases)
@@ -44,7 +45,7 @@
- Minor bugs fixed
## 0.6.21-5 (2024-01-12)
- ⚠ MAJOR CHANGE : switch to the new config logic from homeassistant. Your configuration files will have migrated from /config/hassio_addons/calibre-web to a folder only accessible from my Filebrowser addon called /addon_configs/something-calibre-web. This avoids the addon to mess with your homeassistant configuration folder, and allows to backup the options. Migration of data, custom configs, and custom scripts should be automatic. Please be sure to update all your links however ! For more information, see here : https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/ https://github.com/alexbelgium/hassio-addons/issues/1177
- ⚠ MAJOR CHANGE : switch to the new config logic from homeassistant. Your configuration files will have migrated from /config/hassio_addons/calibre-web to a folder only accessible from my Filebrowser addon called /app_configs/something-calibre-web. This avoids the addon to mess with your homeassistant configuration folder, and allows to backup the options. Migration of data, custom configs, and custom scripts should be automatic. Please be sure to update all your links however ! For more information, see here : https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/ https://github.com/alexbelgium/hassio-addons/issues/1177
## 0.6.21-2 (2023-11-05)

View File

@@ -77,8 +77,8 @@ image: ghcr.io/alexbelgium/calibre_web-{arch}
ingress: true
init: false
map:
- addon_config:rw
- all_addon_configs:rw
- app_config:rw
- all_app_configs:rw
- homeassistant_config:rw
- media:rw
- share:rw

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 07308545.6 (2026-09-05)
- Update to latest version from aaddrick/claude-desktop-debian (changelog : https://github.com/aaddrick/claude-desktop-debian/releases)

View File

@@ -63,7 +63,7 @@ magic link into the in-session Chromium (not a phone).
- Add-on Configuration → `additional_apps: chromium`, restart (installed by
`rootfs/etc/cont-init.d/80-configuration.sh`).
- Add the two `xdg-settings`/`xdg-mime` commands to the custom script
`/addon_configs/db21ed7f_claude-desktop/claude_desktop.sh` (the image ships no standalone
`/app_configs/db21ed7f_claude-desktop/claude_desktop.sh` (the image ships no standalone
terminal).
---

View File

@@ -34,7 +34,7 @@ hassio_role: manager
homeassistant_api: true
auth_api: true
map:
- addon_config:rw
- app_config:rw
- share:rw
- media:rw
- ssl

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,3 +1,5 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
- Fix persistent data directory: the entrypoint now writes to the `/config` mount (the container-side path of the `app_config` map) instead of an unmounted `/app_configs/cleanuparr` path, so Cleanuparr's data survives container recreation.
## 2.10.5 (2026-08-13)
- Update to latest version from Cleanuparr/Cleanuparr (changelog : https://github.com/Cleanuparr/Cleanuparr/releases)

View File

@@ -63,7 +63,7 @@ COPY bashio-standalone.sh /usr/local/lib/bashio-standalone.sh
RUN chmod 0755 /usr/local/lib/bashio-standalone.sh
# Bypass the original /entrypoint.sh (which mounts /config as VOLUME).
# Our wrapper symlinks /app/config → HA addon_config and starts ./Cleanuparr directly.
# Our wrapper symlinks /app/config → HA app_config and starts ./Cleanuparr directly.
ENTRYPOINT ["/ha_entrypoint.sh"]
CMD ["./Cleanuparr"]

View File

@@ -71,7 +71,7 @@ ingress_stream: true
init: false
panel_icon: mdi:movie-search
map:
- addon_config:rw
- app_config:rw
name: Cleanuparr
options:
env_vars: []

View File

@@ -5,7 +5,7 @@ set -e
###############################################################################
# Home Assistant Addon entrypoint for Cleanuparr
# The .NET app uses /app/config as its data directory.
# We symlink /app/config → /addon_configs/cleanuparr (HA persistent storage)
# We symlink /app/config → /config (the HA app_config mount, HA persistent storage)
# and start ./Cleanuparr directly, bypassing the original /entrypoint.sh
# which would trigger the /config Docker VOLUME mount.
###############################################################################
@@ -26,7 +26,7 @@ if [ -d /etc/cont-init.d ]; then
fi
# ─── Setup persistent data directory ─────────────────────────────────────────
HA_DATA_DIR="/addon_configs/cleanuparr"
HA_DATA_DIR="/config"
echo "[Cleanuparr] Setting up data directory: $HA_DATA_DIR"
mkdir -p "$HA_DATA_DIR"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 19.20.5 (2026-09-05)
- Update to latest version from coderaiser/cloudcmd (changelog : https://github.com/coderaiser/cloudcmd/releases)
@@ -234,7 +235,7 @@
## 16.17.7-3 (2023-11-19)
- Minor bugs fixed
- MAJOR CHANGE : new HA config logic implemented. Files are now located in the addon config file, that can be accessed from the addon_configs folder from my cloudcommander or cloudcommander addons. Migration of data, custom configs, and custom scripts should be automatic. Please be sure to update all your links however ! For more information, see here : https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/
- MAJOR CHANGE : new HA config logic implemented. Files are now located in the addon config file, that can be accessed from the app_configs folder from my cloudcommander or cloudcommander addons. Migration of data, custom configs, and custom scripts should be automatic. Please be sure to update all your links however ! For more information, see here : https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/
- Homeassistant config accessible in /homeassistant folder ; all addons config in /addons_config ; this addon config in /config
## 16.17.7-2 (2023-10-30)

View File

@@ -69,8 +69,8 @@ image: ghcr.io/alexbelgium/cloudcommander-{arch}
ingress: true
ingress_stream: true
map:
- addon_config:rw
- all_addon_configs:rw
- app_config:rw
- all_app_configs:rw
- homeassistant_config:rw
- ssl:rw
- share:rw

View File

@@ -35,7 +35,7 @@ fi
# Create symlinks with legacy folders
if [ -d /homeassistant/addons_config ]; then
ln -s /homeassistant/addons_config /config
find /addon_configs/ -maxdepth 1 -mindepth 1 -type d -not -name "*cloudcommander*" -exec ln -s {} /config/addons_config/ \;
find /app_configs/ -maxdepth 1 -mindepth 1 -type d -not -name "*cloudcommander*" -exec ln -s {} /config/addons_config/ \;
fi
if [ -d /homeassistant/addons_autoscripts ]; then
ln -s /homeassistant/addons_autoscripts /config

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,3 +1,4 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 2.2.11 (2026-08-29)
- Update to latest version from ajslater/codex (changelog : https://github.com/ajslater/codex/releases)

View File

@@ -91,7 +91,7 @@ cifsdomain: "WORKGROUP"
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables through the `addon_config` mapping:
This addon supports custom scripts and environment variables through the `app_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.

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