From a92674cb4d8ca32f8ef09a8a35a6492582e7386d Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:51:27 +0100 Subject: [PATCH] Enhance global_var.sh with improved logging and injection Updated logging messages and improved script structure for clarity. Added support for injecting environment variables into bash configuration files. --- .templates/00-global_var.sh | 220 +++++++++++++++++++++++++++--------- 1 file changed, 164 insertions(+), 56 deletions(-) diff --git a/.templates/00-global_var.sh b/.templates/00-global_var.sh index 683110edb..4e3e2c141 100755 --- a/.templates/00-global_var.sh +++ b/.templates/00-global_var.sh @@ -3,24 +3,41 @@ set -e +# ----------------------------------------------------------------------------- +# Guard: only run inside Supervisor-managed add-ons +# ----------------------------------------------------------------------------- if ! bashio::supervisor.ping 2>/dev/null; then echo "..." exit 0 fi echo "" -bashio::log.notice "This script converts all addon options to environment variables. Custom variables can be set using env_vars." +bashio::log.notice "This script converts add-on options (options.json) into environment variables." +bashio::log.notice "Custom variables can be set using the env_vars option." bashio::log.notice "Additional informations : https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2" echo "" +# ----------------------------------------------------------------------------- +# Inputs / outputs +# ----------------------------------------------------------------------------- JSONSOURCE="/data/options.json" ENV_FILE="/.env" +ETC_ENV_FILE="/etc/environment" -if [[ ! -f "$ENV_FILE" ]]; then - printf '# Generated by 00-global_var.sh from %s\n' "$JSONSOURCE" > "$ENV_FILE" +if [[ ! -f "$JSONSOURCE" ]]; then + bashio::exit.nok "Missing ${JSONSOURCE}" fi -# mktemp +if ! command -v jq >/dev/null 2>&1; then + bashio::exit.nok "jq is required but not found" +fi + +mkdir -p /etc +touch "$ETC_ENV_FILE" + +# ----------------------------------------------------------------------------- +# mktemp helper (safe temp file creation) +# ----------------------------------------------------------------------------- mktemp_safe() { local tmpdir="${TMPDIR:-/tmp}" mkdir -p "$tmpdir" || return 1 @@ -30,7 +47,10 @@ mktemp_safe() { printf '%s\n' "$tmpfile" } -# Define secrets location (optional) +# ----------------------------------------------------------------------------- +# Secrets support: +# - If an option value is "!secret foo", try to resolve it from secrets.yaml +# ----------------------------------------------------------------------------- SECRETSOURCE="" if [[ -f /homeassistant/secrets.yaml ]]; then SECRETSOURCE="/homeassistant/secrets.yaml" @@ -38,12 +58,20 @@ elif [[ -f /config/secrets.yaml ]]; then SECRETSOURCE="/config/secrets.yaml" fi -# Injection block markers (single block, idempotent) +# ----------------------------------------------------------------------------- +# Injection block markers: +# 1) EXPORT_BLOCK_*: injected into run scripts / bashrc so services inherit vars +# 2) FILE_BLOCK_* : written into /.env and /etc/environment (idempotent) +# ----------------------------------------------------------------------------- BLOCK_BEGIN="# --- BEGIN ADDON ENV (generated) ---" BLOCK_END="# --- END ADDON ENV (generated) ---" +FILE_BLOCK_BEGIN="# --- BEGIN ADDON ENV FILE (generated) ---" +FILE_BLOCK_END="# --- END ADDON ENV FILE (generated) ---" + EXPORT_BLOCK_FILE="$(mktemp_safe)" -trap 'rm -f "$EXPORT_BLOCK_FILE"' EXIT +ENV_KV_FILE="$(mktemp_safe)" +trap 'rm -f "$EXPORT_BLOCK_FILE" "$ENV_KV_FILE"' EXIT { echo "${BLOCK_BEGIN}" @@ -51,7 +79,9 @@ trap 'rm -f "$EXPORT_BLOCK_FILE"' EXIT echo "${BLOCK_END}" } > "${EXPORT_BLOCK_FILE}" +# ----------------------------------------------------------------------------- # Protected variables that should not be overwritten +# ----------------------------------------------------------------------------- declare -A PROTECTED_VARS=( ["PATH"]=1 ["HOME"]=1 @@ -133,6 +163,8 @@ resolve_secret_if_needed() { } append_export_line_for_injection() { + # Insert one export line right before BLOCK_END in EXPORT_BLOCK_FILE. + # (kept as-is to preserve behavior; safe and idempotent block replacement) local key="$1" local value="$2" local quoted @@ -156,6 +188,9 @@ is_shell_run_script() { } inject_block_into_file() { + # Inject/replace the generated export block in a target script: + # - If the block exists: replace it. + # - If not: insert it right after the shebang (if present), else at top. local file="$1" local tmp tmp="$(mktemp_safe)" @@ -198,12 +233,13 @@ inject_block_into_file() { } update_scripts_with_block() { + # Inject the export block into common service scripts and entrypoints. local f local -A seen=() shopt -s nullglob - # Added /etc/s6-overlay/s6-rc.d/*/run for newer S6 implementation (optional) + # Includes legacy and newer s6 locations for f in /etc/services.d/*/run /etc/services.d/*/*run* /etc/cont-init.d/*.sh /etc/s6-overlay/s6-rc.d/*/run /*/entrypoint.sh /entrypoint.sh; do [[ -f "$f" ]] || continue [[ -n "${seen[$f]:-}" ]] && continue @@ -220,7 +256,53 @@ update_scripts_with_block() { shopt -u nullglob } +replace_block_in_file() { + # Replace (or add) a generated block inside a plain text file. + # Used for /.env and /etc/environment to prevent infinite append growth. + local file="$1" + local begin="$2" + local end="$3" + local content_file="$4" + local tmp + + tmp="$(mktemp_safe)" + + if [[ ! -f "$file" ]]; then + touch "$file" + fi + + awk -v bfile="$content_file" -v begin="$begin" -v end="$end" ' + function print_block() { + while ((getline l < bfile) > 0) print l + close(bfile) + } + BEGIN { inblock=0; printed=0 } + { + if ($0 == begin) { + inblock=1 + if (!printed) { print_block(); printed=1 } + next + } + if ($0 == end) { inblock=0; next } + if (inblock) next + print $0 + } + END { if (!printed) print_block() } + ' "$file" > "$tmp" + + cat "$tmp" > "$file" + rm -f "$tmp" +} + export_option() { + # Core exporter: + # - validates key + # - resolves secrets + # - logs (redacted if sensitive unless verbose) + # - exports into current process + # - exports into s6 environment + # - queues key/value for generated /.env and /etc/environment blocks + # - adds export into injection block for scripts local key="$1" local value="$2" @@ -244,75 +326,101 @@ export_option() { export "${key}=${value}" + # Export for s6 services (preferred way) if [[ -d /var/run/s6/container_environment ]]; then printf '%s' "${value}" > "/var/run/s6/container_environment/${key}" fi - echo "${key}=$(dotenv_quote "$value")" >> "$ENV_FILE" 2>/dev/null || true - mkdir -p /etc - echo "${key}=$(dotenv_quote "$value")" >> /etc/environment 2>/dev/null || true + # Queue dotenv-style line for writing once (idempotent file update later) + echo "${key}=$(dotenv_quote "$value")" >> "$ENV_KV_FILE" 2>/dev/null || true + # Ensure scripts/services also see it (block injection) append_export_line_for_injection "$key" "$value" } -mapfile -t arr < <(jq -r 'keys[]' "${JSONSOURCE}") +# ----------------------------------------------------------------------------- +# One jq pass emits normalized key/value pairs: +# - exports all scalar top-level options (strings/numbers/bools) +# - skips objects/arrays (except env_vars) +# - supports env_vars formats: +# 1) [{name:"FOO", value:"bar"}, ...] +# 2) [{FOO:"bar", BAZ:"qux"}, ...] +# 3) ["FOO=bar", ...] (value may contain '=') +# ----------------------------------------------------------------------------- +while IFS= read -r -d $'\0' key && IFS= read -r value; do + export_option "$key" "$value" +done < <( + jq -r ' + def emit(k; v): "\((k|tostring))\u0000\((v|tostring))"; -for KEYS in "${arr[@]}"; do - jtype="$(jq -r --arg k "$KEYS" '.[$k] | type' "$JSONSOURCE")" + # --- 1) env_vars[] --- + ( .env_vars? // [] )[] as $ev + | if ($ev | type) == "object" then + if ($ev | has("name") and has("value")) then + emit($ev.name; ($ev.value // "")) + else + ($ev | to_entries[] | emit(.key; (.value // ""))) + end + else + ($ev | tostring) as $s + | if ($s | test("^[^=]+=")) then + ($s | capture("^(?[^=]+)=(?.*)$")) as $m + | emit($m.k; $m.v) + else empty end + end + , + # --- 2) top-level scalars excluding env_vars --- + to_entries[] + | select(.key != "env_vars") + | select((.value|type) != "array" and (.value|type) != "object" and (.value|type) != "null") + | emit(.key; .value) + ' "$JSONSOURCE" +) - if [[ "$jtype" == "array" ]]; then - if [[ "$KEYS" == "env_vars" ]]; then - mapfile -t env_entries < <(jq -c '.env_vars[]?' "$JSONSOURCE") - for entry in "${env_entries[@]}"; do - if [[ "$entry" == \{* ]]; then - env_name="$(jq -r 'if has("name") and has("value") then .name else empty end' <<<"$entry")" - if [[ -n "$env_name" ]]; then - env_value="$(jq -r '.value // "" | tostring' <<<"$entry")" - export_option "$env_name" "$env_value" - else - mapfile -t env_keys < <(jq -r 'keys[]' <<<"$entry") - for env_key in "${env_keys[@]}"; do - env_value="$(jq -r --arg k "$env_key" '.[$k] // "" | tostring' <<<"$entry")" - export_option "$env_key" "$env_value" - done - fi - else - env_pair="$(jq -r '.' <<<"$entry")" - if [[ "$env_pair" == *=* ]]; then - export_option "${env_pair%%=*}" "${env_pair#*=}" - else - bashio::log.warning "env_vars entry '$env_pair' is not in KEY=VALUE format, skipping" - fi - fi - done - else - bashio::log.warning "Option '${KEYS}' is an array, skipping" - fi - elif [[ "$jtype" == "object" ]]; then - bashio::log.warning "Option '${KEYS}' is an object, skipping" - elif [[ "$jtype" == "null" ]]; then - continue - else - VALUE="$(jq -r --arg k "$KEYS" '.[$k] // "" | tostring' "$JSONSOURCE")" - export_option "$KEYS" "$VALUE" - fi -done +# ----------------------------------------------------------------------------- +# Write /.env and /etc/environment in an idempotent way (no infinite appends) +# ----------------------------------------------------------------------------- +# Ensure /.env has a header (only if missing) +if [[ ! -f "$ENV_FILE" ]]; then + printf '# Generated by 00-global_var.sh from %s\n' "$JSONSOURCE" > "$ENV_FILE" +fi +ENV_FILE_BLOCK="$(mktemp_safe)" +trap 'rm -f "$EXPORT_BLOCK_FILE" "$ENV_KV_FILE" "$ENV_FILE_BLOCK"' EXIT + +{ + echo "${FILE_BLOCK_BEGIN}" + echo "# Do not edit: generated from ${JSONSOURCE}" + cat "$ENV_KV_FILE" 2>/dev/null || true + echo "${FILE_BLOCK_END}" +} > "$ENV_FILE_BLOCK" + +replace_block_in_file "$ENV_FILE" "$FILE_BLOCK_BEGIN" "$FILE_BLOCK_END" "$ENV_FILE_BLOCK" +replace_block_in_file "$ETC_ENV_FILE" "$FILE_BLOCK_BEGIN" "$FILE_BLOCK_END" "$ENV_FILE_BLOCK" + +# ----------------------------------------------------------------------------- +# Inject generated export block into service scripts and interactive shells +# ----------------------------------------------------------------------------- update_scripts_with_block -# --- MINIMAL CHANGE: also inject into /etc/bash.bashrc (for interactive bash shells) -mkdir -p /etc +# System-wide interactive bash shells touch "/etc/bash.bashrc" inject_block_into_file "/etc/bash.bashrc" + +# Common per-user interactive bash shell config (more standard than bash.bashrc) if [[ -n "${HOME:-}" ]]; then mkdir -p "$HOME" + touch "$HOME/.bashrc" + inject_block_into_file "$HOME/.bashrc" + + # Kept for compatibility with images that use this non-standard name touch "$HOME/bash.bashrc" inject_block_into_file "$HOME/bash.bashrc" fi -################ -# Set timezone # -################ +# ----------------------------------------------------------------------------- +# Set timezone (kept identical in behavior) +# ----------------------------------------------------------------------------- set +eu if [ -n "$TZ" ] && [ -f /etc/localtime ]; then