Files
hassio-addons/elasticsearch/rootfs/usr/local/bin/addon-init.sh
alexbelgium 02bbfa86c3 fix(elasticsearch): force fresh image pull, clarify non-root failure
The published 8.19.18 images are correct (verified: real ES 8.19.18,
run as root, migration + privilege-drop in place). But some upgrades
were left running a stale cached Elasticsearch 7.17.9 image that starts
as uid 1000, producing the reported "mv: cannot move '/data/config' ...
Permission denied" and "AccessDeniedException[.../data/nodes/0]".

- Bump version to 8.19.18-3 to force Home Assistant / Docker to pull a
  fresh image tag instead of reusing the cached one.
- Add an explicit root check on the first init pass (before any move or
  chown) so a non-root start fails with a clear, actionable message
  instead of the cryptic permission error, and wrap the config-archive
  mv with the same clear failure. The re-exec'd uid-1000 pass returns
  before this check, so the privilege drop still works.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 13:35:03 +02:00

183 lines
8.4 KiB
Bash
Executable File

#!/bin/bash
# shellcheck shell=bash
# Sourced by /usr/local/bin/docker-entrypoint.sh (right after "set -e"),
# before Elasticsearch starts. The container starts as root (see
# Dockerfile) so this script can chown/move pre-existing /data content
# that may be owned by root from earlier installs. Elasticsearch itself
# refuses to run as root, and unlike 7.x the upstream 8.x entrypoint no
# longer drops privileges on its own, so this script does it at the end
# (section 6) by re-execing the entrypoint as uid 1000. On that re-exec'd
# pass this script just returns immediately (see the guard right below).
#
# 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)
# 5. Record the running version once Elasticsearch is confirmed healthy
# 6. Drop root privileges before Elasticsearch actually starts
if [ -n "${_ADDON_INIT_REEXEC:-}" ]; then
return 0
fi
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"
# This first pass must be root so it can relocate and take ownership of
# pre-existing /data content written by an earlier (root) install. If it
# is not root (e.g. an old cached image that pinned USER 1000:0, or the
# container being forced to another user), the moves/chowns below fail
# with a cryptic "Permission denied"; fail loudly with the real reason.
if [ "$(id -u)" -ne 0 ]; then
echo "FATAL: the Elasticsearch add-on must start as root (currently uid $(id -u))."
echo "If you upgraded from an older version, the running image is likely stale - fully stop and update/reinstall the add-on so Home Assistant pulls the current image."
exit 1
fi
############################
# 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
if ! mv "$PERSISTENT_HOME/config" "$config_backup"; then
echo "FATAL: could not archive the old config to $config_backup."
echo "This add-on must run as root to migrate a previous install. Restore a Home Assistant backup and ensure the add-on is not forced to a non-root user."
exit 1
fi
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
############################
# 6 Drop privileges #
############################
# Elasticsearch refuses to start as root ("can not run elasticsearch as
# root"). 7.x's own entrypoint dropped to uid 1000 via chroot before
# launching Elasticsearch; 8.x no longer does that, so do it here instead,
# then let the entrypoint continue as uid 1000 (matches the sys_chroot /
# setuid / setgid capabilities already granted in the AppArmor profile).
if [ "$(id -u)" -eq 0 ]; then
export _ADDON_INIT_REEXEC=1
exec chroot --userspec=1000:0 / /usr/local/bin/docker-entrypoint.sh "$@"
fi