Compare commits

..

5 Commits

Author SHA1 Message Date
alexbelgium
94f59489f0 fix(elasticsearch): stay root at runtime, fix upgrade permission failure
Reported: on upgrade from an existing 7.17.9 install, the add-on failed
to start with "mv: cannot move '/data/config' to
'/data/config.bak-7.17.9': Permission denied".

Root cause: a previous fix in this same release restored `USER 1000:0`
at the end of the Dockerfile to match the upstream base image's own
final USER directive. But the upstream 8.19 entrypoint no longer drops
privileges itself (confirmed: it execs elasticsearch directly, no
gosu/chroot dance), and existing installs have /data owned by root
(7.17.9's default image variant runs fully as root). A non-root
container can never chown or move that data.

Revert to root at runtime, matching how this add-on always ran and
matching its own AppArmor profile (chown, setuid, setgid, sys_chroot,
mount capabilities — all meaningless for a non-root process anyway).
Root stays required for the build-time entrypoint patch too, unchanged
from the prior fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 12:11:26 +02:00
alexbelgium
cc427f0c20 fix(elasticsearch): treat HTTP 401 as healthy in the migration marker check
curl -f treated any 4xx as failure, including 401. Users who enable
xpack.security (a supported override via ES_SETTING_XPACK_SECURITY_ENABLED)
got 401 on the unauthenticated healthcheck request, so the version marker
was never written and every restart re-logged the one-time migration
notice. Read the HTTP status directly and accept 200 or 401.

Reviewed and skipped: the cp -rn merge-into-existing-directory concern —
verified empirically (both locally and against the image's Debian/GNU
coreutils base) that GNU cp merges correctly into a pre-existing
same-named destination without nesting; the existing test suite already
exercises this exact path (legacy 7.x data preserved during migration).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 11:26:43 +02:00
alexbelgium
68120a84da fix(elasticsearch): address review — build user, env_vars validation
- The 8.19.18 base image ends the build as USER 1000:0 with a
  root-owned, read-only (0555) entrypoint, so the sed patch and later
  chmod/package-install steps failed. Switch to root for the build and
  restore the Elasticsearch user before runtime.
- Tighten the env_vars name check to require a leading letter/underscore
  (shell identifier rules) instead of allowing a leading digit, which
  made `export "$name"=...` fail and abort startup under `set -e`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 11:18:49 +02:00
alexbelgium
776d063161 fix(elasticsearch): upgrade to 8.19.18 with automatic data migration
The add-on reported version 8.14.3 but the shipped image was still
Elasticsearch 7.17.9 — the Dockerfile BUILD_UPSTREAM was never bumped,
and the builder uses that ARG. The homeassistant-elasticsearch
integration requires 8.14+, so configuration failed (#2849).

- Upgrade to Elasticsearch 8.19.18 (latest 8.x; 9.x cannot read indices
  created in 7.x)
- Add automatic 7.x -> 8.x data migration with a guard that aborts on
  unsupported paths (downgrade, or data more than one major behind).
  The version marker is written only after ES answers on 9200, so a
  failed upgrade never masks the true on-disk data lineage
- Default xpack.security.enabled=false to preserve plain-HTTP behavior
  the HA component expects; override via ES_SETTING_XPACK_SECURITY_ENABLED
- Fix the env_vars option, which never worked (the image has no
  s6-overlay, so the cont-init stack never ran)
- Remove the ingest-attachment plugin install (bundled since ES 8.0,
  which broke the 8.x build)
- Replace line-number-based entrypoint patching with a proper init
  script sourced via a pattern-anchored injection
- Add updater.json pinned to the 8.19 line to prevent version drift and
  accidental 9.x jumps

Fixes #2849

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 11:07:24 +02:00
alexbelgium
c53263a29d Fix BirdNET-Pi standalone mode: strip sudo at build time + ensure /run/php
The build-time hook that removes `sudo` from the BirdNET-Pi scripts was
injected at line 2 of newinstaller.sh, i.e. before the repo is cloned, so it
was a no-op and `sudo` remained in 25 scripts. Outside Home Assistant this
breaks every script invoked by a non-sudoers user (php-fpm's `caddy` user for
the WebUI System Controls, or `abc`) with "X is not in the sudoers file",
which is what prevented standalone (no-Supervisor) operation. Move the strip
to run after the installer clones the repo. Also drop the unused (and, in this
fork, incorrect) `$my_dir` -> `/config` rewrite.

Additionally create /run/php before starting PHP-FPM: it is normally created
by systemd-tmpfiles, which does not run in this container, so on a fresh/tmpfs
/run the socket cannot be bound and the WebUI never comes up.

Verified against the published 2026.07.10 image: the WebUI serves HTTP 200 and
restart_services.sh runs cleanly as the non-sudoers `caddy` user.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:08:41 +02:00
12 changed files with 279 additions and 29 deletions

51
.github/workflows/pr2841-diagnostic.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
---
name: Claude Add-on Build Diagnostic
on:
pull_request:
branches:
- master
jobs:
diagnose:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Gather add-on information
id: information
uses: frenck/action-addon-information@v1.4
with:
path: ./claude_desktop/
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build aarch64 and capture tail
shell: bash
run: |
set +e
BUILD_FROM="$(jq -r '.build_from.aarch64 // empty' '${{ steps.information.outputs.build }}')"
docker buildx build \
--platform linux/arm64 \
--progress=plain \
--build-arg "BUILD_FROM=${BUILD_FROM}" \
--file claude_desktop/Dockerfile \
claude_desktop >build.log 2>&1
status=$?
tail -n 300 build.log >build-tail.log
printf 'build_status=%s\n' "$status" >build-status.txt
exit 0
- name: Upload diagnostic log
uses: actions/upload-artifact@v4
with:
name: claude-aarch64-build-log
path: |
build-tail.log
build-status.txt
retention-days: 1

View File

@@ -1,3 +1,6 @@
## 2026.07.10-1 (10-07-2026)
- Fix standalone (no-Supervisor) mode: strip `sudo` from BirdNET-Pi scripts at build time (the previous hook ran before the repo was cloned, so `sudo` remained and broke service/WebUI actions run by non-sudoers users such as `caddy`/`abc`)
- Ensure `/run/php` exists before starting PHP-FPM so the WebUI starts even when `/run` is a fresh tmpfs
## 2026.07.10 (10-07-2026)
- Minor bugs fixed
## 2026.06.01 (19-06-2026)

View File

@@ -74,17 +74,6 @@ RUN \
curl -f -L -s -S "https://raw.githubusercontent.com/alexbelgium/BirdNET-Pi/main/newinstaller.sh" -o /newinstaller.sh && \
chmod 777 /newinstaller.sh && \
\
# Use installer to modify other scripts
#######################################
# Define file
sed -i "1a /./newinstallermod.sh" /newinstaller.sh && \
echo '#!/bin/bash' >> /newinstallermod.sh && \
# Remove all instances of sudo from all other scripts
echo 'for file in $(grep -srl "sudo" $HOME/BirdNET-Pi/scripts); do sed -i "s|sudo ||" "$file"; done' >> /newinstallermod.sh && \
echo 'for file in $(grep -srl "my_dir" $HOME/BirdNET-Pi/scripts); do sed -i "s|\$my_dir|/config|" "$file"; done' >> /newinstallermod.sh && \
# Set permission
chmod +x /newinstallermod.sh && \
\
# Modify installer
##################
# Use my repository
@@ -110,6 +99,16 @@ RUN \
# Execute installer
/./newinstaller.sh && \
\
# Remove sudo from the installed BirdNET-Pi scripts.
# Inside the container everything already runs with the privileges it needs
# (root during init, user "pi" for the services), so "sudo" is never required.
# Leaving it in breaks any script invoked by a user that is not in sudoers -
# e.g. php-fpm's "caddy" user driving the WebUI System Controls, or the "abc"
# user - which is what prevented BirdNET-Pi from working in standalone
# (no-Supervisor) mode. This must run AFTER the installer has cloned the repo;
# the previous build-time hook ran before the clone and was therefore a no-op.
for file in $(grep -srlF "sudo " "$HOME/BirdNET-Pi/scripts"); do sed -i "s|sudo ||g" "$file"; done && \
\
# Install dateparser and resampy, upgrade numpy
$PYTHON_VIRTUAL_ENV /usr/bin/pip3 install dateparser resampy && \
\

View File

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

View File

@@ -4,6 +4,14 @@
# Correct /config permissions after startup
chown pi:pi /config
# Ensure the PHP-FPM runtime directory exists. It is normally created by
# systemd-tmpfiles (/usr/lib/tmpfiles.d/php*-fpm.conf), which does not run in
# this container - so on a fresh/tmpfs /run the socket cannot be bound and the
# WebUI never starts. Create it here so the web stack works with or without
# Home Assistant Supervisor.
mkdir -p /run/php
chown www-data:www-data /run/php 2> /dev/null || true
# Set timezone without requiring Home Assistant Supervisor or D-Bus.
TZ_VALUE="${TZ:-}"
if [[ -S /var/run/dbus/system_bus_socket ]] && command -v timedatectl > /dev/null 2>&1; then

View File

@@ -1,3 +1,17 @@
## 8.19.18 (2026-07-14)
- Upgrade to Elasticsearch 8.19.18 (#2849). Note: despite the previous add-on version reading `8.14.3`, the shipped image was still Elasticsearch 7.17.9 — the Dockerfile upstream version was never bumped. This release actually delivers 8.x, making the add-on compatible with the `homeassistant-elasticsearch` integration (requires 8.14+).
- Automatic data migration: existing 7.17 data is upgraded in place by Elasticsearch on first start (one-way; can take a while on large datasets). A migration guard aborts with a clear message on unsupported paths (downgrades, or data more than one major version old). Take a Home Assistant backup before updating.
- The previous bundled config directory is archived to `/data/config.bak-<old-version>` during major upgrades; re-apply custom settings to the new config if needed.
- Security (`xpack.security.enabled`) defaults to `false` to preserve the previous plain-HTTP behavior. Override by adding `ES_SETTING_XPACK_SECURITY_ENABLED` (or any `ES_SETTING_XPACK_SECURITY_*` variable) in the add-on's `env_vars` option.
- Fixed the `env_vars` add-on option, which previously had no effect: variables are now exported before Elasticsearch starts.
- Removed the `ingest-attachment` plugin install: it is a bundled module since Elasticsearch 8.0.
- Startup persistence logic rewritten as a proper init script (`/usr/local/bin/addon-init.sh`) instead of line-number-based entrypoint patching.
- Added `updater.json` so upstream 8.19.x releases are tracked automatically (pinned to the 8.19 line: 9.x cannot read indices created in 7.x).
- The upstream 8.x image ends the build as a non-root user with a read-only entrypoint; the Dockerfile now switches to root for the build steps that patch/install into it. The image stays root at runtime too (unchanged from 7.17.9): the upstream entrypoint no longer drops privileges itself, and `addon-init.sh` needs to chown/move pre-existing `/data` content that may be owned by root from earlier installs.
- `env_vars` names starting with a digit are now rejected before export instead of crashing the entrypoint.
- Fixed a startup failure (`mv: cannot move '/data/config' ... Permission denied`) on upgrade from an existing 7.17.9 install, caused by an earlier fix in this same release that switched the runtime user to non-root before this fix was in place.
## 8.14.3-3 (2026-06-19)
- Fix startup failing with `chroot: cannot change root directory` by allowing `capability sys_chroot` in the AppArmor profile (#2709)
- Fix AppArmor profile name (was `inadyn_addon`, colliding with several other add-ons); renamed to `elasticsearch_addon`

View File

@@ -14,9 +14,14 @@
# 1 Build Image #
#################
ARG BUILD_UPSTREAM="7.17.9"
ARG BUILD_UPSTREAM="8.19.18"
FROM elasticsearch:$BUILD_UPSTREAM
# The base image ends as USER 1000:0 with a root-owned, read-only (0555)
# entrypoint; switch back to root for the remaining build steps (entrypoint
# patch, package install, chmod), then restore the Elasticsearch user below
USER root
##################
# 2 Modify Image #
##################
@@ -26,20 +31,15 @@ ENV S6_CMD_WAIT_FOR_SERVICES=1 \
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
S6_SERVICES_GRACETIME=0
# Expose the upstream version to the add-on init script (migration guard)
ARG BUILD_UPSTREAM
ENV UPSTREAM_VERSION="$BUILD_UPSTREAM"
# Data persistence
# hadolint ignore=SC2016
RUN sed -i '5a echo "Data location moved. Please wait while elasticsearch starts..."' /usr/local/bin/docker-entrypoint.sh \
&& sed -i '5a chown -R $(id -u):$(id -g) $HOME' /usr/local/bin/docker-entrypoint.sh \
&& sed -i '5a done' /usr/local/bin/docker-entrypoint.sh \
&& sed -i '5a ln -s $NEWHOME/$file /usr/share/elasticsearch || true' /usr/local/bin/docker-entrypoint.sh \
&& sed -i '5a rm -r /usr/share/elasticsearch/$file || true' /usr/local/bin/docker-entrypoint.sh \
&& sed -i '5a cp -rn /usr/share/elasticsearch/$file $NEWHOME || true' /usr/local/bin/docker-entrypoint.sh \
&& sed -i '5a for file in "data" "config"; do' /usr/local/bin/docker-entrypoint.sh \
&& sed -i '5a mkdir -p $NEWHOME' /usr/local/bin/docker-entrypoint.sh \
&& sed -i '5a NEWHOME="/data"' /usr/local/bin/docker-entrypoint.sh \
# Install plugins
&& /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch ingest-attachment
# Data persistence & migration: source the add-on init script at the top of
# the official entrypoint (pattern-anchored; ingest-attachment is bundled
# since ES 8.0 so no plugin install is needed anymore)
RUN sed -i '/^set -e$/a . /usr/local/bin/addon-init.sh' /usr/local/bin/docker-entrypoint.sh \
&& grep -q "addon-init.sh" /usr/local/bin/docker-entrypoint.sh
##################
# 3 Install apps #
@@ -62,7 +62,7 @@ COPY ha_automodules.sh /ha_automodules.sh
RUN chmod 744 /ha_automodules.sh && /ha_automodules.sh "$MODULES" && rm /ha_automodules.sh
# Manual apps
ENV PACKAGES=""
ENV PACKAGES="jq"
# Automatic apps & bashio
COPY ha_autoapps.sh /ha_autoapps.sh
@@ -140,3 +140,10 @@ HEALTHCHECK \
--start-period=30s \
--timeout=25s \
CMD curl -A "HealthCheck: Docker/1.0" -s -f "http://127.0.0.1:${HEALTH_PORT}${HEALTH_URL}" &>/dev/null || exit 1
# Stay root at runtime: addon-init.sh must chown/mv pre-existing /data
# content that may be owned by root from earlier installs, and the upstream
# entrypoint no longer drops privileges itself, so a non-root container
# can't touch that data at all. This matches the addon's own AppArmor
# profile (chown, setuid, setgid, sys_chroot, mount capabilities), which
# assumes a root process.

View File

@@ -91,6 +91,22 @@ Connect other applications to Elasticsearch using:
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.
Elasticsearch settings can be set through variables named `ES_SETTING_<SETTING_WITH_UNDERSCORES>`; for example `ES_SETTING_XPACK_SECURITY_ENABLED` maps to `xpack.security.enabled`.
### Security
To preserve the plain-HTTP behavior of previous versions (and compatibility with the Home Assistant Elasticsearch integration), `xpack.security.enabled` defaults to `false`. To enable Elasticsearch security, add `ES_SETTING_XPACK_SECURITY_ENABLED` with value `true` in `env_vars`.
## Upgrading from 7.x
The upgrade to Elasticsearch 8.x is automatic and **one-way**:
1. Take a Home Assistant backup of the add-on before updating.
2. Update the add-on and start it. Elasticsearch upgrades the existing indices in place on first start — this can take a while on large datasets; do **not** stop the add-on during the first start.
3. The previous bundled config directory is archived to `/data/config.bak-<old-version>`; re-apply any custom settings to the new config.
Downgrading afterwards is not supported by Elasticsearch — restore the backup instead.
## Integration with HA
Component : https://community.home-assistant.io/t/elasticsearch-component-publish-home-assistant-events-to-elasticsearch/66877

View File

@@ -90,4 +90,4 @@ slug: elasticsearch
startup: services
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/elasticsearch
version: 8.14.3-3
version: 8.19.18

View File

@@ -1 +0,0 @@
#!/bin/bash

View File

@@ -0,0 +1,143 @@
#!/bin/bash
# shellcheck shell=bash
# Sourced by /usr/local/bin/docker-entrypoint.sh (right after "set -e"),
# before Elasticsearch starts. Runs as root (the image stays root at
# runtime - see Dockerfile); the official entrypoint does not drop
# privileges itself, and Elasticsearch ends up running as root too.
#
# Responsibilities:
# 1. Export user env_vars from /data/options.json
# 2. Default xpack.security.enabled=false (7.x behavior) unless user overrides
# 3. Relocate data & config to /data for persistence (idempotent)
# 4. Guard major-version data migrations (7.x -> 8.x is automatic)
echo "-----------------------------------------------------------"
echo " Add-on: Elasticsearch server"
echo " Upstream version: ${UPSTREAM_VERSION:-unknown}"
echo "-----------------------------------------------------------"
ES_HOME="/usr/share/elasticsearch"
PERSISTENT_HOME="/data"
VERSION_MARKER="$PERSISTENT_HOME/.addon-upstream-version"
OPTIONS_JSON="/data/options.json"
############################
# 1 Export user env_vars #
############################
if [ -f "$OPTIONS_JSON" ] && command -v jq >/dev/null 2>&1; then
while IFS= read -r pair; do
name=$(jq -r '.name // empty' <<<"$pair")
value=$(jq -r '.value // empty' <<<"$pair")
if [[ $name =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
echo "Setting env variable from options: $name"
export "$name"="$value"
elif [ -n "$name" ]; then
echo "WARNING: ignoring invalid env_vars name: $name"
fi
done < <(jq -c '.env_vars[]?' "$OPTIONS_JSON" 2>/dev/null || true)
fi
##################################
# 2 Security default (7.x parity)#
##################################
# ES 8+ enables security + TLS by default, which breaks plain-http clients
# such as the homeassistant-elasticsearch component. Keep the previous 7.x
# behavior unless the user explicitly configures xpack.security themselves
# (either as a dotted setting or via the ES_SETTING_* translation).
if ! env | grep -qiE '^(xpack\.security\.|ES_SETTING_XPACK_SECURITY_)'; then
export ES_SETTING_XPACK_SECURITY_ENABLED=false
echo "Security: xpack.security.enabled=false (default; override by setting ES_SETTING_XPACK_SECURITY_ENABLED in env_vars)"
fi
############################
# 3 Migration guard #
############################
current_version="${UPSTREAM_VERSION:-0.0.0}"
current_major="${current_version%%.*}"
data_version=""
if [ -f "$VERSION_MARKER" ]; then
data_version="$(head -n 1 "$VERSION_MARKER" | tr -cd '0-9.')"
elif [ -d "$PERSISTENT_HOME/data" ] && [ -n "$(ls -A "$PERSISTENT_HOME/data" 2>/dev/null)" ]; then
# Existing data without a marker: only 7.17.9 was ever shipped before markers
data_version="7.17.9"
fi
if [ -n "$data_version" ] && [[ $current_major =~ ^[0-9]+$ ]]; then
data_major="${data_version%%.*}"
if [ "$data_major" -gt "$current_major" ]; then
echo "FATAL: existing data was written by Elasticsearch $data_version but this add-on runs $current_version."
echo "Downgrading Elasticsearch data is not supported. Restore a Home Assistant snapshot taken with the newer version, or delete the add-on data to start fresh."
exit 1
elif [ "$((current_major - data_major))" -gt 1 ]; then
echo "FATAL: existing data was written by Elasticsearch $data_version, which is more than one major version behind $current_version."
echo "Elasticsearch can only upgrade data from the previous major version. Upgrade stepwise (e.g. $data_major.x -> $((data_major + 1)).x -> ...) or delete the add-on data to start fresh."
exit 1
elif [ "$data_major" -lt "$current_major" ]; then
echo "NOTICE: one-time automatic data migration from Elasticsearch $data_version to $current_version."
echo "NOTICE: indices are upgraded automatically on startup. This can take a while on large datasets - do NOT stop the add-on during the first start."
# The bundled config from the old major is stale (jvm.options, log4j2,
# security settings). Archive it so a fresh one is seeded below.
if [ -d "$PERSISTENT_HOME/config" ] && [ ! -L "$PERSISTENT_HOME/config" ]; then
config_backup="$PERSISTENT_HOME/config.bak-$data_version"
if [ ! -e "$config_backup" ]; then
mv "$PERSISTENT_HOME/config" "$config_backup"
echo "NOTICE: previous config archived to $config_backup. Re-apply any custom settings to the new config."
fi
fi
# The container config dir may still symlink to the archived config
if [ -L "$ES_HOME/config" ]; then
rm -f "$ES_HOME/config"
fi
fi
fi
############################
# 4 Data persistence #
############################
mkdir -p "$PERSISTENT_HOME"
for dir in "data" "config"; do
if [ ! -L "$ES_HOME/$dir" ]; then
if [ -d "$ES_HOME/$dir" ]; then
cp -rn "$ES_HOME/$dir" "$PERSISTENT_HOME" 2>/dev/null || true
rm -rf "${ES_HOME:?}/$dir"
fi
mkdir -p "$PERSISTENT_HOME/$dir"
ln -s "$PERSISTENT_HOME/$dir" "$ES_HOME/$dir"
fi
done
# Make the persisted files usable by the elasticsearch user (uid 1000),
# which the official entrypoint drops to when started as root
if [ "$(id -u)" -eq 0 ]; then
chown -R 1000:0 "$PERSISTENT_HOME/data" "$PERSISTENT_HOME/config" 2>/dev/null || true
fi
echo "Data location: $PERSISTENT_HOME (persistent). Please wait while elasticsearch starts..."
############################
# 5 Record data version #
############################
# Only record the running version once ES is confirmed healthy, so a failed
# upgrade attempt never masks the true on-disk data lineage
if [ "$data_version" != "$current_version" ]; then
(
for _ in $(seq 1 180); do
# Check the HTTP status directly instead of curl -f: a 401 means
# Elasticsearch is up and answering (security just requires
# auth), so it must count as healthy too, not as a failure.
status=$(curl -A "HealthCheck: Docker/1.0" -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:9200" 2>/dev/null || true)
if [ "$status" = "200" ] || [ "$status" = "401" ]; then
echo "$current_version" >"$VERSION_MARKER"
echo "Elasticsearch $current_version started successfully; data version recorded."
exit 0
fi
sleep 10
done
) &
fi

View File

@@ -0,0 +1,10 @@
{
"github_fulltag": false,
"github_tagfilter": "v8.19",
"last_update": "14-07-2026",
"repository": "alexbelgium/hassio-addons",
"slug": "elasticsearch",
"source": "github",
"upstream_repo": "elastic/elasticsearch",
"upstream_version": "8.19.18"
}