- Grant NET_ADMIN capability and /dev/net/tun device so the ZEROTIER option
works at runtime (Codex review)
- Use a JSON boolean for updater.json "paused" to match the documented format
(CodeRabbit review)
- Document the ZeroTier requirements in README and CHANGELOG
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXZfzWxhkWHq8i8P7fnDvL
Implements the Zoraxy general-purpose reverse proxy (issue #1946) as a new
add-on following the repository conventions:
- Based on the upstream zoraxydocker/zoraxy image (Alpine)
- Web management UI exposed on port 8000 (webui link); reverse proxy on 80/443
- Persistent config/db/logs/plugins relocated to /config (addon_config) so they
survive add-on updates, by patching the upstream entrypoint working directory
- Options NOAUTH/ZEROTIER/FASTGEOIP/MDNS mapped to upstream env vars via the
shared 00-global_var module, plus env_vars passthrough for advanced settings
- Standard 6-section Dockerfile, supervised services.d launcher, healthcheck
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXZfzWxhkWHq8i8P7fnDvL
Both variants share the same image; aligning the version ensures the
Supervisor pulls the updated build that suppresses the misleading
NET_RAW/NET_ADMIN alert for Full Access users.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WfZ5JKAR2NHP1KjoAGyBBD
Under the Home Assistant Supervisor the required NET_RAW/NET_ADMIN
capabilities are granted, but NetAlertX's upstream capabilities-audit
script cannot read them and prints a "🚨 ALERT: capabilities are missing"
banner. Users repeatedly mistook this informational message for the cause
of unrelated issues. Neutralise the audit script at build time (kept in
place as a no-op for the entrypoint runner) and bump version/changelog.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WfZ5JKAR2NHP1KjoAGyBBD
The folded block scalar was ~532 chars, exceeding the CodeRabbit v2
schema constraint and causing the config to be rejected. Trimmed to
238 chars; detailed context already lives in path_instructions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195h5b5zBHAA6urgJtp4aJP
Configure CodeRabbit (.coderabbit.yaml) tailored to this Home Assistant
add-on repository:
- tone_instructions establishing the HA add-on context for both issue
help and PR reviews
- chat.auto_reply + knowledge_base (issues/PRs/learnings) to support
issue triage and first-line help
- path_instructions encoding the add-on conventions (config.yaml,
Dockerfile, S6 cont-init/services scripts, updater.json, CHANGELOG,
workflows) from CLAUDE.md
- labeling/path filters aligned with the repo's existing labels and
generated artifacts
- review tools matching CI: shellcheck, hadolint, markdownlint,
yamllint, actionlint, gitleaks, checkov
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195h5b5zBHAA6urgJtp4aJP
- netbird-server: new profile `netbird-server_addon` (includes capability sys_chroot)
- netalertx_fa: symlink apparmor.txt to base netalertx profile, matching the
repo's existing _fa convention (e.g. scrutiny_fa -> scrutiny)
- signalk intentionally left without a profile (config sets apparmor: false)
No version bumps — profiles apply on each add-on's next update.
Co-Authored-By: Claude <noreply@anthropic.com>
- Add `capability sys_chroot` to all add-on AppArmor profiles (prophylactic
fix; required by any service that uses privilege-separation chroot, e.g.
sshd, Elasticsearch JVM, postgres)
- Fix AppArmor profile name collisions where add-ons had copy-pasted a wrong
profile name (inadyn_addon, db21ed7f_qbittorrent, radarr_addon,
db21ed7f_scrutiny, addon_db21ed7f_emby_nas, addon_updater, chromium_addon,
fireflyiii_addon, webtop_addon, joplin, gitea_addon) causing AppArmor to
silently apply the wrong profile
No version bumps — profiles apply on next add-on update.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V7P3Nbem7n9FqGTXrLMadP
Gitea's install wizard uses an atomic SaveTo that replaces the symlink
at /data/gitea/conf/app.ini with a real file containing the completed
install config. On the next restart /config/app.ini (the pre-wizard
template) already exists, so the previous guard skipped the copy and
the rm deleted the real installed config, wiping DB/security settings.
Always copy the real file over /config/app.ini regardless of whether
the target already exists.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D46Ef3nZZdbVuwUpPhCd4T
Symlink /data/gitea/conf/app.ini -> /config/app.ini so users can read
and edit the full Gitea configuration via the HA file editor without
needing shell access. Existing installs migrate their app.ini on first
restart. Closes#1907.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D46Ef3nZZdbVuwUpPhCd4T
Both add-ons failed because their AppArmor profiles did not list
`capability sys_chroot`, which AppArmor then denied even though it is
part of Docker's default capability set:
- gitea: sshd privilege-separation chroot("/var/empty") failed with
"Operation not permitted [preauth]", breaking git-over-SSH (#2653)
- elasticsearch: upstream startup chroot failed with
"chroot: cannot change root directory" (#2709)
Also rename the elasticsearch AppArmor profile from the copy-pasted
`inadyn_addon` (shared with several other add-ons) to
`elasticsearch_addon` to avoid profile-name collisions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V7P3Nbem7n9FqGTXrLMadP
Upstream paperless-ngx switched to an s6-overlay v3 init system at v2.15.0
(ENTRYPOINT ["/init"]) and removed the legacy /sbin/docker-entrypoint.sh that
this add-on patched. The add-on had therefore been broken since the 2.15.x bump.
- Inject initialization via S6_STAGE2_HOOK=/ha_entrypoint.sh instead of patching
the now-removed upstream entrypoint
- Export runtime variables to the s6 container_environment so the upstream
supervised services (svc-webserver, svc-worker, svc-scheduler, svc-consumer)
pick them up
- Use the canonical ha_entrypoint.sh template (remove the outdated bundled copy)
- Add 00-global_var module + jq for env_vars passthrough
- Guard the ImageMagick policy patch and the optional nginx/redis startup
- Bump version and updater tracking to 2.20.15
tr reads from the infinite /dev/urandom stream; head exits after N bytes,
closing the pipe, which sends SIGPIPE to tr (exit 141). With set -euo pipefail
at the top of 99-run.sh, pipefail surfaces that as the script exit code and
the container never starts. Suppress it with || true on both occurrences in
the MinIO credential generation block.
https://claude.ai/code/session_01MaLKhb2CJiF9Fb3Dyr585r
- Rename AppArmor profile from the leftover qbittorrent name to ente_addon
to avoid colliding with the qbittorrent add-on's profile
- Map the Accounts (3001), Auth (3003) and Cast (3004) ports so the login,
2FA and cast web apps served by nginx are actually reachable
- Default the external Postgres port to 5432 when DB_PORT is left blank
- Write the resolved DB host/port to museum.yaml so external databases are
configured correctly on disk, not just via env overrides
- Exclude minio-data and postgres from Home Assistant backups to avoid
pulling the whole photo library and database into every backup
https://claude.ai/code/session_01MaLKhb2CJiF9Fb3Dyr585r
1. Generate random MinIO credentials on first run, persist to /config/minio-creds,
reuse on restart. Export MINIO_ROOT_USER/PASSWORD env vars for MinIO server.
2. Make nginx web startup idempotent by checking if web.bak exists before moving.
3. Bind MinIO console to 127.0.0.1:9001 with --console-address.
4. Expose port 3002 (albums) in config.yaml and derive ENTE_ALBUMS_ORIGIN from
the API endpoint host with the mapped external port 8302.
Add a dedicated nginx health endpoint on 127.0.0.1:3001 with access_log
off that returns 200 at /health. Update HEALTHCHECK to verify both that
the filebrowser process is running (pgrep) and nginx is serving (curl to
health endpoint), avoiding direct HTTP requests to filebrowser that caused
log spam every 5 seconds.
Adds clear step-by-step instructions in the Mounting Drives section
of all Immich addon READMEs explaining how to use a mounted local
disk for Immich storage by combining localdisks and data_location.
Removing /config/config.yaml after a failed first-boot curl left the
next yq read (.realtime.audio.export.path) trying to open a missing
file; under set -e that aborts the entire cont-init script, so the
addon would never get to seed its defaults or start BirdNET-Go.
Seed an empty YAML document ({}) instead. The existing "//=" defaults
block then populates output.sqlite.path, logging.file_output.*, and
the migration block writes realtime.audio.export.path. Result: an
offline first boot now produces a valid minimal config.yaml and the
container starts cleanly.
Also harden the yq read with "// """ so a freshly seeded "{}" doc
returns an empty string (caught by the existing :-DEFAULT fallback)
rather than the literal string "null".
Replace mqtt_disable / mariadb_disable (opt-out, default-on) with
mqtt_auto_config / mariadb_auto_config (opt-in, default-off). When the
HA addon is detected but the option is off, still log the broker /
database credentials and a hint pointing the user at the option — so
discoverability stays the same without surprise config rewrites.
Bugs fixed in 01-structure.sh:
- Database backup created during BIRDSONGS_FOLDER migration was written
to the script's CWD instead of /config, and the restore path was
recomputed with a fresh timestamp — so any second-boundary crossing
between backup and restore left the user unable to recover. Backup
path is now absolute and reused for restore.
- Path inputs are validated against [A-Za-z0-9._/-]+ before being
interpolated into the SQL UPDATE statement.
- Default-config download tolerates network failure instead of leaving
an empty config.yaml behind.
- output.sqlite.path and logging.file_output.* are now seeded with the
"set-if-missing" idiom (`//=`) so user edits to config.yaml survive
restarts. (Breaking: addon options for log rotation now only seed
defaults on first run.)
- Path normalization centralized; trailing-slash juggling removed.
UX upgrades:
- 33-mqtt.sh now auto-configures realtime.mqtt.* in config.yaml from
the HA Mosquitto addon (with new mqtt_disable opt-out option).
- 33-mariadb.sh now auto-switches output.mysql.* to the HA MariaDB
addon and disables SQLite (with mariadb_disable opt-out option).
Cleanup:
- Dockerfile: upstream entrypoint sed-patch now warns (not silently
succeeds) when the target pattern is missing in a new nightly.
- Removed dead nginx upstream.conf pointing at unused port 8096.
- Trimmed redundant nginx HTML-attribute sub_filters; upstream
birdnet-go handles those itself via X-Ingress-Path. JS string
rewrites kept since the upstream HTML rewriter does not touch JS.
(Breaking UI-side if upstream regresses — see CHANGELOG.)
- Change cronupdate shebang from bashio to /bin/bash (cron PATH lacks bashio)
- Remove bashio API calls from cron script (no Supervisor access in cron)
- Source /.env in cron script to load all env_vars from 00-global_var.sh
- Persist SILENT_MODE to /etc/environment for cron access
- Remove destructive `sed 's|root|www-data|g'` on /etc/crontab
- Fix /etc/environment permissions from 600 to 644 for cron readability
/etc/asound.conf is read-only in the addon environment, so both the
shipped overrides and the user-supplied override are now written to
/root/.asoundrc (the app runs as root with HOME=/root). ALSA loads
~/.asoundrc as an additive layer on top of the system config, so the
override behavior is unchanged.
Add addon options to control birdnet-go log file rotation:
- LOG_MAX_SIZE_MB (default: 50): maximum size per log file before rotation
- LOG_MAX_AGE_DAYS (default: 7): maximum days to retain old log files
On startup, the addon:
1. Configures birdnet-go's logging.file_output settings in config.yaml
2. Sets max_rotated_files to 3 and enables compression
3. Trims existing log files exceeding the configured age
Fixes#1922
Advanced users who legitimately need JACK, a custom dsnoop chain, or any
other ALSA setup can drop their own asound.conf into the addon config
folder. The cont-init script copies it over /etc/asound.conf before
launching the app, replacing the addon-shipped defaults.
- Patch upstream entrypoint via sed so chmod on the read-only /dev/snd
mount no longer prints "Read-only file system" lines.
- Add /etc/asound.conf overriding the JACK, OSS, dsp, and dsnoop PCM
plugins to type "null" with hint.show off. This hides them from
snd_device_name_hint(), so miniaudio's device enumeration no longer
probes them at launch and the corresponding libjack/pcm_oss/pcm_dsnoop
errors disappear.
Two build-time bugs prevented the immich image from being built:
1. The find . chmod command ran from WORKDIR=/ and descended into
/app/immich/server/node_modules/, hitting files it could not chmod
(exit code 1 at build step 5). Fixed by scoping find to only
/etc /usr/local/bin /usr/local/lib /usr/local/share — the actual
directories populated by COPY rootfs/ /.
2. sed -i tried to patch /etc/s6-overlay/s6-rc.d/init-test-run/run
which no longer exists in the imagegenius base image (removed in
an earlier upstream update). Fixed with a [ -f ... ] guard so the
sed is silently skipped when the file is absent.
Reproduced both bugs locally with docker build, verified fix on a
bare-metal VPS — all 17 build steps complete cleanly.
Fixes#2718
When image: is set in config.yaml, HA pulls that image directly and
never runs the Dockerfile, so the rootfs overlay (including the
passthrough entrypoint) was never applied. Removing image: forces HA
to build from the Dockerfile, which COPYs rootfs/ and applies our
run script and entrypoint override.
Also switch run script from symlink approach to AURRAL_DATA_DIR env
var, which avoids the race condition where docker-entrypoint.sh runs
before s6 and tries to chown a broken symlink target.
/data is HA's built-in private persistent storage for every addon - no
user configuration needed. Only download_folder needs to be user-facing
since that's where the music lives and users need to know the path.
The upstream docker-entrypoint.sh chowns /app/backend/data which fails
when it's a symlink to a host-mounted HA path. Instead, use the env vars
that Aurral natively supports to redirect paths:
- AURRAL_DATA_DIR -> data_folder config option
- DOWNLOAD_FOLDER -> download_folder config option
- WEEKLY_FLOW_FOLDER -> download_folder/weekly-flow
This lets the entrypoint chown the real (container-internal) /app/backend/data
unmolested, while node writes persistent data directly to the HA share paths.
Previously image: pointed directly at the upstream ghcr.io/lklynet/aurral,
meaning the Dockerfile was never built and the rootfs overlay (including the
entrypoint fix) was never applied. Changed to ghcr.io/alexbelgium/aurral-{arch}
which is where the CI build pushes the image built FROM the upstream via build.json.
docker-entrypoint.sh runs chown -R on /app/backend/data which fails when
that path is a symlink to a host-mounted HA volume. Replace it with a
passthrough script that just exec's its arguments, letting the s6 run
script handle all setup.
The upstream docker-entrypoint.sh runs chown -R on /app/backend/data before
the s6 run script can replace it with a symlink. When the symlink target is a
host-mounted HA path (/share, /data, etc.), the chown fails with
"Operation not permitted".
Overriding ENTRYPOINT with /init hands control directly to s6-overlay, which
then runs the run script that creates the symlinks before node starts.
- config.yaml: strip back to download_folder, data_folder, port only
- README.md: match alexbelgium style, merge upstream link into About,
remove badge buttons, remove Navidrome references
- config.yaml: fix url to point to master, add ingress support,
remove download_folder/data_folder from options (set via UI),
match lidarr schema style with env_vars passthrough
- updater.json: add with upstream v1.76.12 tracking lklynet/aurral
- run script: use proper ingress port env var, cleaner startup
- README.md: fix install instructions to reference master branch
Replace the legacy Werkzeug dev server (python -m core.api) with a single
gunicorn GeventWebSocketWorker, matching upstream BirdNET-PiPy 0.7.0's
production-server migration (the add-on already builds 0.7.0 source).
Single worker is mandatory: live detections fan out via an in-process
Socket.IO emit (no Redis message_queue), so >1 worker would silently drop
Live Feed broadcasts. Heavy maintenance jobs cooperatively yield to keep the
worker responsive. Bound to 127.0.0.1:5002 for single-container use. Also
fixes the now-stale 'Python core.api' comment in nginx/run.
Add-on packaging change only -> 0.7.0 -> 0.7.0-1 (suffix convention; base
tracks upstream via the updater bot).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two independent failure modes can cause the restart loop on arm64:
1. /dev/stdout may be inaccessible in some ARM container runtimes; replaced
with /proc/1/fd/1 which LSIO images already use for direct container stdout.
2. s6-notifyoncheck writes to fd 3 on check success, but s6-rc only opens
that fd when notification-fd exists in the service directory. LSIO arm64
images ship svc-qbittorrent without it, so s6-notifyoncheck exits with
EBADF and s6 restarts the service in a loop. Guard with a file check and
fall back to exec'ing qbittorrent-nox directly when the file is absent.
https://claude.ai/code/session_01F16ThtZyfXj6ZKFPkrSAAq
/dev/stdout resolves via /proc/self/fd/1 and can be inaccessible in some
ARM container runtimes, causing the exec redirect to fail and s6 to restart
the service in a loop. /proc/1/fd/1 is the path LSIO images already use
for direct container stdout (see nginx silent-mode handling) and is reliable
across architectures.
https://claude.ai/code/session_01F16ThtZyfXj6ZKFPkrSAAq
s6-rc opens fd 3 for a longrun service only when a notification-fd file is
present in the service directory. The LSIO aarch64 image ships svc-qbittorrent
without that file, so fd 3 is never opened. s6-notifyoncheck exits with EBADF
after its readiness check passes, s6 treats the supervised process as dead and
immediately restarts it — producing the infinite "Starting qBittorrent..." loop.
Check for the notification-fd file at runtime: use the full s6-notifyoncheck
path when it is present (amd64, preserving existing behaviour), and fall back
to exec'ing qbittorrent-nox directly when it is absent (aarch64).
https://claude.ai/code/session_01F16ThtZyfXj6ZKFPkrSAAq
s6-notifyoncheck writes to fd 3 when its readiness check passes, but
the LSIO aarch64 image's svc-qbittorrent service has no notification-fd
file so s6-rc never opens fd 3. s6-notifyoncheck exits with EBADF, s6
sees the supervised process die and immediately restarts it, producing
the infinite "Starting qBittorrent..." loop seen on odroid-c2 and other
aarch64 boards.
Drop s6-notifyoncheck entirely and exec qbittorrent-nox directly under
s6-setuidgid so s6 supervises the real process. Silent mode is handled
by redirecting the shell's fds before exec rather than passing a
/dev/stdout path (avoids a separate class of ARM container fd issues).
https://claude.ai/code/session_01F16ThtZyfXj6ZKFPkrSAAq
Seafile's check_init_admin.py looks for SEAFILE_ADMIN_EMAIL/PASSWORD
in the env, then falls back to conf/admin.txt, and only prompts
interactively if neither is available. The upstream init.sh writes
admin.txt, but it is skipped when conf/ccnet.conf or conf/revision
already exist (e.g. after a partial previous install) and the env
vars do not always reach the seahub subprocess via su. Write
admin.txt directly and inject the values into seafile.env so admin
creation succeeds (#2685).
https://claude.ai/code/session_01EwuoFH7aHJMySr9J775XQP
s6-notifyoncheck exits with EBADF when notification-fd 3 isn't opened by
s6-rc (can happen depending on LSIO image layer order), killing the supervised
qBittorrent process and causing the 2-second restart loop. Dropping it lets
s6 supervise qBittorrent directly without the fragile fd notification path.
Also probe /app, /usr/bin, and /usr/local/bin for the binary so the addon
works across LSIO image builds that place qbittorrent-nox in different spots.
https://claude.ai/code/session_015eiGSjWjSVtKbBFhBHUeDt
On HAOS >=17.3 the Supervisor Docker network gained IPv6, so
core-mariadb resolves to an IPv6 address first. The official MariaDB
addon only grants its service user from the IPv4 supervisor subnet, so
connections from IPv6 fail with "Access denied".
Resolve the hostname to its IPv4 address before connecting in every
addon that consumes bashio::services 'mysql' 'host': photoprism,
monica, fireflyiii, seafile, zoneminder. Fall back to the raw hostname
if resolution fails so IPv4-only setups keep working unchanged.
Replaces the 0.6.6-3 sleep+ingress-file-loop in services.d/nginx/run
with bashio::net.wait_for on 127.0.0.1:5002 (core.api). Under
s6-overlay all services.d/* services start concurrently, so nginx
could accept requests before the API had bound its port — proxy paths
(/api/, /socket.io/, /internal/auth) would 502, and that 502 could be
cached by an upstream service worker / edge cache (e.g. Cloudflare-
fronted HA), leaving the UI blank.
Matches the sister-addon pattern (bazarr, jellyfin, radarr). Also
switches to `exec nginx` for proper s6 supervision of the nginx PID.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bump birdnet-pipy to 0.6.6 (upstream tags v0.6.5 + v0.6.6; skipping
0.6.5 same as the updater bot would).
Minor CHANGELOG.md cleanup while here: normalize 10 entries from
DD-MM-YYYY to YYYY-MM-DD, and remove a stale pre-ingress block (0.2,
0.6.1-0.6.6 dated Jan 2026) that was colliding with real version
numbers further up the file.
When DB_CONNECTION is set to mariadb_addon, the script now checks if the user
has explicitly configured DB_USERNAME, DB_PASSWORD, or DB_DATABASE in addon
options. If set, those values are used instead of the MariaDB addon service
discovery credentials. This fixes authentication failures when the service
account doesn't have proper access.
Fixes: Firefly III access denied for user 'service' issue
Agent-Logs-Url: https://github.com/alexbelgium/hassio-addons/sessions/7cacda5b-d03e-47c5-b4fc-4cfb4ef2a3dc
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
Adds `homeassistant_api: true` so the addon can call HA Core's
`update.install` service. Required for in-app self-update —
Supervisor blocks `/store/addons/<self>/update`, so the backend
routes through Core.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The app derives its timezone from station lat/lng via timezonefinder
(Settings → Location in the Web UI), so a separate addon-level TZ only
ever created mismatch with the UI-derived zone. All app-facing timestamps
(dashboard, API, DB) and Python service stdout (api/main/birdnet) already
honor the UI zone via logging_config.py's formatter, and the frontend log
viewer re-converts Icecast timestamps on read.
Net effect in the HA addon log pane: Python services still show the
correct local time; only Icecast's raw stdout now prints in UTC (accepted
trade for a single source of truth). Deletes
rootfs/etc/cont-init.d/02-timezone.sh added in 0.5.6-2.
Also cleans up the options YAML in DOCS.md/README.md: drops
RECORDING_MODE and RTSP_URL (never wired — app reads them from
user_settings.json), drops http_stream from the modes list, and moves
STREAM_BITRATE under an env_vars: example since it's honored by
start-icecast.sh but not a schema option.
Bumped to 0.6.3-2 (addon-only, no upstream app change).
Upstream 0.6.3 consolidates deployment-path handling into a single
<base href> declaration in index.html with Vite base: './' and
relative paths for all internal URLs. The previous seven sub_filter
rules (href/src/'/api'/'/socket.io') are no longer needed — one
<base href> replacement is sufficient.
Also removes the incidental brittleness from byte-level sub_filter
matches in minified JS bundles (the '/stream/' rule inadvertently
double-prefixed the literal 'api.get("/stream/config")' string).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the nginx rewrite directive that could cause URL normalization/re-encoding
issues with special characters in query strings (like spaces encoded as +).
HA Supervisor strips the ingress prefix before forwarding, making the rewrite
unnecessary. Without it, proxy_pass uses the raw $request_uri which preserves
URL encoding.
Also fix Connection header from hardcoded "upgrade" to $connection_upgrade
map variable for proper WebSocket vs regular HTTP request handling.
Agent-Logs-Url: https://github.com/alexbelgium/hassio-addons/sessions/3982b002-dfcb-4eb5-98c2-913f665b6a07
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
Two fixes for the birdnet-pipy addon:
1. Icecast log permission error: 01-structure.sh creates /app/data/logs
as root, but start-icecast.sh runs via gosu icecast2. The first log
write fails with "Permission denied", crashing icecast in a restart
loop (502 Bad Gateway on the Live Feed page). Fix: chown the log dir
and file to icecast2 before dropping privileges.
2. Live Feed double-prefixed request in ingress mode: the "/stream/"
sub_filter rule also matches 'api.get("/stream/config")' in the JS
bundle, producing a request like
/hassio_ingress/TOKEN/api/hassio_ingress/TOKEN/stream/config (404).
The upstream frontend (BirdNET-PiPy >= 0.6.2) now strips the leading
slash from stream URLs so they resolve via <base href> — the
sub_filter rule is no longer needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The calibre-web addon relies on calibredb to retrieve book metadata for
downloads. Previously, calibre was only installed at runtime via
DOCKER_MODS (linuxserver/mods:universal-calibre), which could fail due
to network issues or changes in the init sequence.
Installing calibre at build time ensures calibredb is always available
in the container, fixing the 500 Internal Server Error when downloading
books.
Agent-Logs-Url: https://github.com/alexbelgium/hassio-addons/sessions/6f6e6795-4a2c-4c6a-88b2-931def081d20
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
The upstream Maintainerr image declares /opt/data as a Docker VOLUME.
Attempting to rm -rf /opt/data fails with "Resource busy" because mount
points cannot be removed. Instead, we now:
1. Copy seed data from /opt/data to /config (persistent storage)
2. Clear contents inside /opt/data (rm -rf /opt/data/*)
3. Symlink each item in /config back into /opt/data
This ensures the VOLUME directory stays intact while all data is
redirected to persistent storage.
Agent-Logs-Url: https://github.com/alexbelgium/hassio-addons/sessions/82a46feb-2e9c-4c40-b193-614167e6d5c3
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
# Max 250 chars. Detailed context lives in path_instructions below.
tone_instructions:"HA add-on repo with 120+ Docker add-ons for Home Assistant Supervisor. Be concise, practical and friendly; most contributors are hobbyists. Link to add-on READMEs and the repo wiki. Follow existing conventions over generic best practices."
early_access:false
enable_free_tier:true
reviews:
# "chill" keeps reviews helpful without nitpicking the many small upstream
# version-bump PRs that dominate this repo.
profile:"chill"
# Don't block merges; this repo merges frequently and relies on CI gating.
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Repository Overview
This is a Home Assistant add-on repository containing 120+ Docker-based add-ons for the Home Assistant Supervisor. Each add-on is a self-contained directory with a Dockerfile, config schema, and S6-overlay init scripts. The repository uses GitHub Actions for CI/CD, linting, and automated upstream version tracking.
## Add-On Directory Structure
Most add-ons follow this common layout, though exceptions exist (e.g. some archived add-ons use `config.json` instead of `config.yaml`, some add-ons have `build.yaml` instead of `build.json` or no build file at all, and not every add-on includes a `rootfs/` tree):
```
addon_name/
├── config.yaml # HA add-on metadata, schema, ports, maps
├── build.json # Base Docker images per architecture (may be build.yaml, or absent)
├── CHANGELOG.md # Required; must be updated on every PR
└── rootfs/ # Optional; absent in some add-ons
└── etc/
├── cont-init.d/ # S6-overlay init scripts (numbered, run in order)
└── services.d/ # S6-overlay supervised services (some add-ons use
# s6-overlay v3 layout at etc/s6-overlay/s6-rc.d/ instead)
```
## Dockerfile Convention
Most Dockerfiles follow this 6-section pattern (some add-ons deviate slightly, e.g. using a pinned upstream image directly instead of `ARG BUILD_FROM`):
4.**Entrypoint**– Set `S6_STAGE2_HOOK=/ha_entrypoint.sh`
5.**Labels**– Standard OCI + HA labels from build args
6.**Healthcheck**– curl-based check suppressed from nginx/apache logs
Shared build-time scripts are pulled from `.templates/` at build time:
-`ha_automodules.sh`– Downloads module scripts listed in `ARG MODULES=`
-`ha_autoapps.sh`– Installs packages listed in `ENV PACKAGES=`
-`ha_entrypoint.sh`– S6 stage-2 hook; launches the cont-init stack at container start
-`ha_lsio.sh`– Patches LinuxServer.io base images for HA compatibility
-`bashio-standalone.sh`– Bashio library for scripts outside Supervisor context
The `ARG MODULES=` line lists template scripts to download at build time (e.g., `00-banner.sh 01-custom_script.sh 00-smb_mounts.sh`). Commonly-used modules in `.templates/` (not exhaustive):
-`00-banner.sh`– Print the add-on startup banner
-`00-global_var.sh`– Initialize global env vars from HA options
-`00-local_mounts.sh`– Mount local disks (localdisks option)
-`00-smb_mounts.sh`– SMB/CIFS network mount support
-`00-deprecated.sh`– Print a deprecation warning for add-ons superseded by official ones
-`01-config_yaml.sh`– Map HA options → app's `config.yaml`
-`01-custom_script.sh`– Run user-provided custom scripts
-`99-custom_script.sh`– Run a user `script.sh` from the add-on config dir at startup
Other helper scripts in `.templates/` used at build/run time: `ha_automatic_packages.sh` (resolve package names across distros), `ha_entrypoint_modif.sh`, `00-aaa_dockerfile_backup.sh`, plus `config.template`/`script.template`/`show_text_color` (templates/assets copied into add-ons).
## config.yaml Schema
Key fields in every add-on's `config.yaml`:
```yaml
arch:[aarch64, amd64]
image:ghcr.io/alexbelgium/{slug}-{arch}
version:"X.Y.Z"# upstream version (format varies; see Versioning section)
ingress:true/false
ingress_port:8000
map:
- addon_config:rw # /addon_configs/<hostname>/
- share:rw
- media:rw
- ssl
schema:
env_vars:# Allows arbitrary env var passthrough
- name:match(^[A-Za-z0-9_]+$)
value:str?
PUID:int
PGID:int
TZ:str?
networkdisks:str? # SMB mounts
localdisks:str? # Local disk mounts
```
The `env_vars` schema key enables the env-var passthrough mechanism. At runtime the `00-global_var.sh` cont-init module reads `/data/options.json` and exports each key as an environment variable (writing to `/.env` and `/etc/environment`). `ha_entrypoint.sh` is the S6 stage-2 hook that launches the cont-init stack but does not itself perform the JSON-to-env conversion.
## Versioning
Add-on versions in `config.yaml` closely follow the upstream release tag and do not conform to a single fixed format. Common patterns include:
-`X.Y.Z`– plain upstream semver (e.g. `0.137.0`)
-`X.Y.Z-N`– upstream version with a local patch counter (e.g. `0.6.26-2`)
When an upstream version is bumped, update `version` in `config.yaml`. If the add-on's `Dockerfile` contains an `ARG BUILD_UPSTREAM` line, update that value too — it is the canonical place that records the upstream version at build time (it is **not** stored in `build.json`/`build.yaml`). Some add-ons do not use `BUILD_UPSTREAM` at all. The `updater.json` file tracks which upstream source/repo to monitor and records the last seen version.
Add-ons support end-user customization without rebuilding the image (see the repo wiki). At startup, `99-custom_script.sh` looks in the add-on's config directory for a user-provided `script.sh` (seeded from `.templates/script.template`) and executes it. Combined with the `env_vars` passthrough and the custom-script modules, this lets users inject commands and environment without forking the add-on.
✓ [Aurral](aurral/) : Self-hosted music discovery, request management, flows, and playlist importing for Lidarr with library-aware recommendations.
@@ -113,12 +119,13 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-badge]
![localdisks][localdisks-badge]
✓ [Bazarr NAS](bazarr/) : Companion application to Sonarr and Radarr to download subtitles
✓ [Bazarr NAS](bazarr/) : Companion application to Sonarr and Radarr to download subtitles
@@ -314,7 +321,7 @@ If you want to do add the repository manually, please follow the procedure highl
![aarch64][aarch64-badge]
![amd64][amd64-badge]
✓  [FileBrowser Quantum](filebrowser_quantum/) : FileBrowser Quantum provides a modern, responsive file manager with multi-source support, advanced authentication options, and realtime indexing for your Home Assistant files.
✓  [FileBrowser Quantum](filebrowser_quantum/) : FileBrowser Quantum provides a modern, responsive file manager with multi-source support, advanced authentication options, and realtime indexing for your Home Assistant files
@@ -324,7 +331,7 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-badge]
![localdisks][localdisks-badge]
✓  [Filebrowser (24134x)](filebrowser/) : filebrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files
✓  [Filebrowser](filebrowser/) : filebrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files
@@ -649,6 +656,12 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-badge]
![localdisks][localdisks-badge]
✓ [Nginx Proxy Manager + Static Web Server](nginx_webserver_proxy/) : Nginx Proxy Manager with a built-in configurable static file server. Manage reverse proxies via NPM UI on port 81 while serving files from HA storage on port 80.
[Aurral](https://github.com/lklynet/aurral) is a self-hosted music discovery, request management, flows, and playlist importing app for Lidarr with library-aware recommendations.
This addon is based on the docker image <https://github.com/lklynet/aurral>
## Configuration
| Option | Default | Description |
|---|---|---|
| `download_folder` | `/share/aurral/downloads` | Path where Aurral writes flow downloads. Must be under `/share`. |
| `weekly_flow_folder` | `weekly-flow` | Subfolder name appended to `download_folder` for weekly flow files. The full path will be `download_folder/weekly_flow_folder`. |
## Installation
1. Add my add-ons repository to your home assistant instance (in supervisor addons store at top right, or click button below if you have configured my HA)
[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons)
2. Install this add-on.
3. Click the `Save` button to store your configuration.
4. Set the `download_folder` option to your preferred path.
5. Optionally set `weekly_flow_folder` to customise the weekly flow subfolder name.
6. Start the add-on.
7. Check the logs of the add-on to see if everything went well.
- MQTT auto-config now also enables BirdNET-Go's native Home Assistant MQTT auto-discovery: detection sensors appear in Home Assistant automatically with no manual YAML (existing UI/config.yaml edits are preserved)
- MQTT auto-config seeds `realtime.mqtt.retain: true` (only when unset) so sensor states survive Home Assistant restarts
- Added supervisor watchdog (tcp://[HOST]:[PORT:8080]) so the add-on is automatically restarted if BirdNET-Go stops responding
- Added backup_exclude for rotated logs, making Home Assistant backups smaller (SQLite journals are kept so hot backups stay consistent)
## nightly-20260615 (2026-06-17)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260601-2 (03-06-2026)
- Minor bugs fixed
## nightly-20260601 (2026-06-01)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260524 (2026-05-30)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260429-405 (2026-05-30)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260525-3 (28-05-2026)
- New `mqtt_auto_config` addon option (default `false`). When `true` and the Home Assistant MQTT addon is active, `realtime.mqtt.{enabled,broker,username,password}` are written directly to `config.yaml` on every restart. When `false` but Mosquitto is detected, the addon still logs the broker details and reminds you about the option — nothing is written.
- New `mariadb_auto_config` addon option (default `false`). When `true` and the Home Assistant MariaDB addon is active, `output.mysql.*` is filled in and `output.sqlite.enabled` is set to `false`. When `false` but MariaDB is detected, the addon logs the credentials and reminds you about the option.
- **Breaking**: `output.sqlite.path` and `logging.file_output.*` are now seeded only when missing from `config.yaml` (previously overwritten every restart). Values changed through the BirdNET-Go UI or by hand-editing `config.yaml` now survive container restarts. If you relied on `LOG_MAX_SIZE_MB` / `LOG_MAX_AGE_DAYS` addon options to override an existing setting in `config.yaml`, remove the existing key from `config.yaml` or edit it directly — the option will only be applied on first run.
- **Breaking (UI only)**: The nginx ingress reverse-proxy no longer rewrites HTML `href`/`src`/`action` attributes; upstream BirdNET-Go handles those itself via `X-Ingress-Path`. JavaScript string-literal rewrites are unchanged. Please file an issue if you see broken images, links, or forms in the ingress UI after upgrade.
- Fix database-migration restore: the timestamped backup created during a `BIRDSONGS_FOLDER` change was being written to the script's working directory and looked up under a fresh timestamp on restore, so a SQL failure left the user unable to recover. Backup path is now absolute and reused for restore.
- Harden the `BIRDSONGS_FOLDER` SQL/YAML path substitution: paths containing characters outside `[A-Za-z0-9._/-]` are now rejected up front instead of being interpolated raw into the SQL UPDATE statement.
- Tolerate a missing internet connection on first boot: if the default `config.yaml` cannot be downloaded from GitHub, the init script now seeds an empty YAML document so the addon-defaults block populates a usable config (rather than aborting the script on the next `yq` call under `set -e`).
- Warn (without failing the build) if the upstream `entrypoint.sh` patch target drifts in a new nightly.
- Remove a dead nginx upstream definition that pointed at an unused port.
## nightly-20260525-2 (26-05-2026)
- Suppress noisy startup logs: silence `chmod /dev/snd` errors on the read-only HA mount, and hide unavailable ALSA plugins (JACK, OSS, dsnoop) from device enumeration so libjack and pcm_oss/dsnoop probes no longer print at launch. ALSA overrides are written to `/root/.asoundrc` (since `/etc/asound.conf` is read-only in this environment).
- Allow advanced users to override the ALSA config by dropping a custom `asound.conf` into the addon config folder.
- Add LOG_MAX_SIZE_MB and LOG_MAX_AGE_DAYS addon options to manage log storage size
- Automatically trim log files exceeding configured age on startup
## nightly-20260525 (26-05-2026)
- Minor bugs fixed
## nightly-20260511-414-2 (22-05-2026)
- Minor bugs fixed
## nightly-20260511-414 (2026-05-16)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260429-405 (2026-05-02)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260321-397 (2026-03-26)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
BirdNET-Go can be integrated with Home Assistant using a MQTT Broker.
> **💡 Easiest path — automatic discovery.** If you run the Home Assistant
> Mosquitto (MQTT) addon, just set `mqtt_auto_config: true` in this add-on's
> options. The add-on then wires in the broker credentials **and** enables
> BirdNET-Go's native Home Assistant MQTT auto-discovery, so the detection
> sensors appear in Home Assistant automatically with **no manual YAML at
> all**. The manual sensor/template/card configuration below is only needed if
> you want to build your own custom entities instead of (or in addition to) the
> auto-discovered ones.
## MQTT Configuration
Your Home Assistant must be setup with MQTT and BirdNET-Go MQTT integration must be enabled. Modify the BirdNET-Go config.yaml file to enable MQTT. If you are using the Mosquitto Broker addon, you will see a log message during the BirdNET-Go startup showing the internal MQTT server details needed for configuration similar to below.
@@ -46,9 +46,14 @@ Options can be configured through three ways :
- Addon options
```yaml
ALSA_CARD :number of the card (0 or 1 usually), see https://github.com/tphakala/birdnet-go/blob/main/doc/installation.md#deciding-alsa_card-value
TZ:Etc/UTC specify a timezone to use, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
COMMAND :realtime --rtsp url# allows to provide arguments to birdnet-go
BIRDSONGS_FOLDER:/config/clips# where audio clips are stored (can be on a mounted drive)
LOG_MAX_SIZE_MB:50# max log file size before rotation
LOG_MAX_AGE_DAYS:7# max log retention in days
homeassistant_microphone:false# when true, force audio source to "default" (HA microphone)
env_vars:[]# extra environment variables to pass to the container
TZ:Etc/UTC# timezone, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
mqtt_auto_config:false# set true to auto-wire the Home Assistant MQTT addon into config.yaml
mariadb_auto_config:false# set true to auto-wire the Home Assistant MariaDB addon into config.yaml (also disables SQLite)
```
- Config.yaml
@@ -57,6 +62,14 @@ Additional variables can be configured using the config.yaml file found in /conf
- Config_env.yaml
Additional environment variables can be configured there
### MQTT and MariaDB auto-configuration (opt-in)
If the Home Assistant **MQTT** addon is installed and running and you set `mqtt_auto_config: true` in the addon options, the addon writes the HA Mosquitto credentials directly into BirdNET-Go's `config.yaml` on every startup: `realtime.mqtt.enabled`, `broker`, `username`, and `password` are populated, and the topic defaults to `birdnet`. In addition, it enables BirdNET-Go's **native Home Assistant MQTT auto-discovery** (`realtime.mqtt.homeassistant.enabled`), so the detection sensors show up in Home Assistant automatically — **no manual MQTT sensor YAML required** (the hand-written sensors in [HAINTEGRATION.md](./HAINTEGRATION.md) remain available if you prefer to build your own). Messages are also retained (`realtime.mqtt.retain: true`) so sensor states survive Home Assistant restarts. When the option is `false` (the default), the addon still logs the broker details and reminds you about the option whenever Mosquitto is detected — nothing is written.
If the Home Assistant **MariaDB** addon is installed and running and you set `mariadb_auto_config: true`, the addon writes the HA credentials into `output.mysql.*` and sets `output.sqlite.enabled` to `false` (database name `birdnet`, created on first connect). When the option is `false` (the default), the addon only logs the credentials so you can configure them manually.
The addon also seeds `output.sqlite.path` and `logging.file_output.*` defaults only when those keys are missing from `config.yaml`, so values you change through the BirdNET-Go UI now survive container restarts.
### Mounting Drives
This addon supports mounting both local drives and remote SMB shares:
bashio::log.warning "Modifying database paths from $CURRENT_BIRDSONGS_FOLDER to $BIRDSONGS_FOLDER. A backup named birdnet.db_$backup will be created before"
BACKUP_FILE="/config/birdnet.db_${backup}"
bashio::log.warning "Modifying database paths from $CURRENT_BIRDSONGS_FOLDER to $BIRDSONGS_FOLDER. A backup will be created at ${BACKUP_FILE}"
# Create backup
if ! cp /config/birdnet.db "birdnet.db_$backup";then
# Create backup at the absolute path we'll restore from on failure.
if ! cp /config/birdnet.db "$BACKUP_FILE";then
bashio::log.error "Failed to create a backup of the database. Aborting path modification."
exit1
fi
# Execute the query using sqlite3
SQL_QUERY="UPDATE notes SET clip_name = '${BIRDSONGS_FOLDER%/}/' || substr(clip_name, length('${CURRENT_BIRDSONGS_FOLDER%/}/') + 1) WHERE clip_name LIKE '${CURRENT_BIRDSONGS_FOLDER%/}/%';"
# Paths were validated above against [A-Za-z0-9._/-]+ so quote
# escaping in the SQL literal is not a concern.
SQL_QUERY="UPDATE notes SET clip_name = '${BIRDSONGS_FOLDER}/' || substr(clip_name, length('${CURRENT_BIRDSONGS_FOLDER}/') + 1) WHERE clip_name LIKE '${CURRENT_BIRDSONGS_FOLDER}/%';"
if ! sqlite3 /config/birdnet.db "$SQL_QUERY";then
bashio::log.warning "An error occurred while updating the paths. The database backup will be restored."
BACKUP_FILE="/config/birdnet.db_$(date +%Y%m%d_%H%M%S)"# Make sure this matches the earlier backup filename
if[ -f "$BACKUP_FILE"];then
mv "$BACKUP_FILE" /config/birdnet.db
bashio::log.info "The database backup has been restored."
else
bashio::log.error "Backup file not found! Manual intervention required."
bashio::log.error "Backup file$BACKUP_FILE not found! Manual intervention required."
fi
else
echo"Paths have been successfully updated."
bashio::log.info"Paths have been successfully updated."
fi
fi
fi
@@ -95,8 +129,34 @@ fi
####################
# Correct Defaults
####################
bashio::log.info "Correcting configuration for defaults"
# Seed addon-specific defaults only if the user has not set them in
# config.yaml. The "//=" form leaves any user-edited value alone, so
# changes made via the BirdNET-Go UI or by hand-editing /config/config.yaml
# survive container restarts.
bashio::log.info "Seeding default configuration values (only if missing)"
bashio::log.info "Seeding default log rotation: max ${LOG_MAX_SIZE_MB}MB per file, max ${LOG_MAX_AGE_DAYS} days retention (only applied if not already set)"
# Seed log-rotation defaults; do not clobber user-edited values.
if ! bashio::config.true 'mariadb_auto_config';then
bashio::log.green "---"
bashio::log.yellow "Home Assistant MariaDB addon detected. Set 'mariadb_auto_config: true' in the addon options to wire it into BirdNET-Go automatically (and disable SQLite). Connection details:"
bashio::log.yellow "Home Assistant MQTT addon detected. Set 'mqtt_auto_config: true' in the addon options to wire it into BirdNET-Go automatically AND enable Home Assistant auto-discovery (sensors appear in HA with no manual YAML). Connection details:"
bashio::log.blue "MQTT user : ${MQTT_USER}"
bashio::log.blue "MQTT password: ${MQTT_PASS}"
bashio::log.blue "MQTT broker : ${MQTT_BROKER}"
bashio::log.green "---"
exit0
fi
if[ ! -f "$CONFIG_LOCATION"];then
bashio::log.warning "Skipping MQTT auto-configuration: $CONFIG_LOCATION not found"
exit0
fi
bashio::log.green "---"
bashio::log.blue "mqtt_auto_config enabled; writing Home Assistant MQTT credentials into BirdNET-Go config"
bashio::log.blue "Broker: ${MQTT_BROKER}"
bashio::log.blue "User: ${MQTT_USER}"
bashio::log.blue "Home Assistant auto-discovery: enabled (sensors appear in HA automatically)"
bashio::log.green "---"
# $broker / $user / $pass / "birdnet" are jq/yq variables and literals,
# not shell expansions, so the single quotes are intentional.
#
# Connection fields (enabled/broker/username/password) are force-set on every
# start so they track the HA MQTT addon's rotating credentials. Topic and the
# homeassistant.* discovery knobs use "//=" so they are only seeded when
# missing. retain is seeded via an explicit has() check rather than "//=",
# because jq treats a user-set "retain: false" as falsy and "//=" would wrongly
# flip it back to true; has() seeds the default only when the key is truly
# absent. Any value the user later changes in the BirdNET-Go UI or config.yaml
# therefore survives restarts. homeassistant.enabled is force-set to true
# because turning on discovery is the whole point of the auto-config option.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.