Files
hassio-addons/elasticsearch/rootfs/usr/local/bin/addon-init.sh
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

144 lines
6.3 KiB
Bash

#!/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