mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-01-10 09:51:02 +01:00
Refactor global variable script to improve readability and maintainability. Added support for handling secrets and environment variables more effectively.
299 lines
8.5 KiB
Bash
Executable File
299 lines
8.5 KiB
Bash
Executable File
#!/usr/bin/with-contenv bashio
|
|
# shellcheck shell=bash
|
|
|
|
set -e
|
|
|
|
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 "Additional informations : https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2"
|
|
echo ""
|
|
|
|
JSONSOURCE="/data/options.json"
|
|
|
|
# Define secrets location (optional)
|
|
SECRETSOURCE=""
|
|
if [[ -f /homeassistant/secrets.yaml ]]; then
|
|
SECRETSOURCE="/homeassistant/secrets.yaml"
|
|
elif [[ -f /config/secrets.yaml ]]; then
|
|
SECRETSOURCE="/config/secrets.yaml"
|
|
fi
|
|
|
|
# Injection block markers (single block, idempotent)
|
|
BLOCK_BEGIN="# --- BEGIN ADDON ENV (generated) ---"
|
|
BLOCK_END="# --- END ADDON ENV (generated) ---"
|
|
|
|
EXPORT_BLOCK_FILE="$(mktemp)"
|
|
trap 'rm -f "$EXPORT_BLOCK_FILE"' EXIT
|
|
|
|
{
|
|
echo "${BLOCK_BEGIN}"
|
|
echo "# Do not edit: generated from ${JSONSOURCE}"
|
|
echo "${BLOCK_END}"
|
|
} > "${EXPORT_BLOCK_FILE}"
|
|
|
|
# Protected variables that should not be overwritten
|
|
declare -A PROTECTED_VARS=(
|
|
["PATH"]=1
|
|
["HOME"]=1
|
|
["PWD"]=1
|
|
["SHLVL"]=1
|
|
["_"]=1
|
|
["S6_BEHAVIOR_IF_STAGE2_FAILS"]=1
|
|
)
|
|
|
|
is_valid_env_name() {
|
|
[[ "$1" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]
|
|
}
|
|
|
|
# Quote for shell *code* (for injection into scripts). Keep punctuation intact.
|
|
# - single-line => single quotes
|
|
# - multi-line => $'...\n...' (one physical line; safe for injection)
|
|
shell_quote_for_code() {
|
|
local s="$1"
|
|
|
|
if [[ "$s" == *$'\n'* || "$s" == *$'\r'* || "$s" == *$'\t'* ]]; then
|
|
s="${s//\\/\\\\}"
|
|
s="${s//\'/\\\'}"
|
|
s="${s//$'\n'/\\n}"
|
|
s="${s//$'\r'/\\r}"
|
|
s="${s//$'\t'/\\t}"
|
|
printf "\$'%s'" "$s"
|
|
return 0
|
|
fi
|
|
|
|
# single-quote with embedded '"'"' for literal '
|
|
s="${s//\'/\'\"\'\"\' }"
|
|
s="${s% }"
|
|
printf "'%s'" "$s"
|
|
}
|
|
|
|
dotenv_quote() {
|
|
# For /.env and /etc/environment: double quotes + minimal escaping
|
|
local v="$1"
|
|
v="${v//\\/\\\\}"
|
|
v="${v//\"/\\\"}"
|
|
v="${v//$'\n'/\\n}"
|
|
v="${v//$'\r'/\\r}"
|
|
printf '"%s"' "$v"
|
|
}
|
|
|
|
resolve_secret_if_needed() {
|
|
local v="$1"
|
|
local name line
|
|
|
|
if [[ "$v" =~ ^[[:space:]]*\!secret[[:space:]]+(.+)[[:space:]]*$ ]]; then
|
|
name="${BASH_REMATCH[1]}"
|
|
name="${name#\"}"; name="${name%\"}"
|
|
name="${name#\'}"; name="${name%\'}"
|
|
|
|
if [[ -z "${SECRETSOURCE}" ]]; then
|
|
bashio::log.warning "Homeassistant config not mounted, secrets are not supported"
|
|
printf '%s' "$v"
|
|
return 0
|
|
fi
|
|
|
|
# Exact key match at start of line; ignore comments
|
|
line="$(
|
|
awk -v k="$name" '
|
|
/^[[:space:]]*#/ {next}
|
|
$0 ~ "^[[:space:]]*" k ":[[:space:]]*" {
|
|
sub("^[[:space:]]*" k ":[[:space:]]*", "", $0)
|
|
print
|
|
exit
|
|
}
|
|
' "$SECRETSOURCE"
|
|
)"
|
|
|
|
[[ -z "$line" ]] && bashio::exit.nok "Secret '${name}' not found in ${SECRETSOURCE}"
|
|
printf '%s' "$line"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s' "$v"
|
|
}
|
|
|
|
append_export_line_for_injection() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local quoted
|
|
quoted="$(shell_quote_for_code "$value")"
|
|
|
|
awk -v k="$key" -v q="$quoted" -v end="$BLOCK_END" '
|
|
$0 == end { print "export " k "=" q }
|
|
{ print }
|
|
' "${EXPORT_BLOCK_FILE}" > "${EXPORT_BLOCK_FILE}.tmp"
|
|
mv -f "${EXPORT_BLOCK_FILE}.tmp" "${EXPORT_BLOCK_FILE}"
|
|
}
|
|
|
|
is_shell_run_script() {
|
|
local f="$1"
|
|
local h
|
|
h="$(head -n 1 "$f" 2>/dev/null || true)"
|
|
|
|
[[ "$h" =~ ^#! ]] || return 1
|
|
[[ "$h" =~ (sh|bash|with-contenv) ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
inject_block_into_file() {
|
|
local file="$1"
|
|
local tmp
|
|
tmp="$(mktemp)"
|
|
|
|
awk -v bfile="${EXPORT_BLOCK_FILE}" -v begin="${BLOCK_BEGIN}" -v end="${BLOCK_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
|
|
|
|
if (NR == 1) {
|
|
if ($0 ~ /^#!/) {
|
|
print $0
|
|
if (!printed) { print_block(); printed=1 }
|
|
next
|
|
} else {
|
|
if (!printed) { print_block(); printed=1 }
|
|
print $0
|
|
next
|
|
}
|
|
}
|
|
print $0
|
|
}
|
|
END {
|
|
if (!printed) print_block()
|
|
}
|
|
' "$file" > "$tmp"
|
|
|
|
cat "$tmp" > "$file"
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
update_scripts_with_block() {
|
|
local f
|
|
local -A seen=()
|
|
|
|
shopt -s nullglob
|
|
|
|
# Added /etc/s6-overlay/s6-rc.d/*/run for newer S6 implementation (optional)
|
|
for f in /etc/services.d/*/run /etc/services.d/*/*run* /etc/cont-init.d/*.sh /etc/s6-overlay/s6-rc.d/*/run; do
|
|
[[ -f "$f" ]] || continue
|
|
[[ -n "${seen[$f]:-}" ]] && continue
|
|
seen["$f"]=1
|
|
|
|
if ! is_shell_run_script "$f"; then
|
|
bashio::log.debug "Skipping non-shell script: $f"
|
|
continue
|
|
fi
|
|
|
|
inject_block_into_file "$f"
|
|
done
|
|
|
|
shopt -u nullglob
|
|
}
|
|
|
|
export_option() {
|
|
local key="$1"
|
|
local value="$2"
|
|
|
|
if [[ -n "${PROTECTED_VARS[$key]:-}" ]]; then
|
|
bashio::log.warning "Skipping protected environment variable: ${key}"
|
|
return 0
|
|
fi
|
|
|
|
if ! is_valid_env_name "$key"; then
|
|
bashio::log.warning "Skipping invalid env var name: ${key}"
|
|
return 0
|
|
fi
|
|
|
|
value="$(resolve_secret_if_needed "$value")"
|
|
|
|
if bashio::config.false "verbose" || [[ "${key,,}" =~ (pass|secret|token|apikey|api_key|private|pwd) ]]; then
|
|
bashio::log.blue "${key}=******"
|
|
else
|
|
bashio::log.blue "${key}=${value}"
|
|
fi
|
|
|
|
export "${key}=${value}"
|
|
|
|
if [[ -d /var/run/s6/container_environment ]]; then
|
|
printf '%s' "${value}" > "/var/run/s6/container_environment/${key}"
|
|
fi
|
|
|
|
echo "${key}=$(dotenv_quote "$value")" >> "/.env" 2>/dev/null || true
|
|
mkdir -p /etc
|
|
echo "${key}=$(dotenv_quote "$value")" >> /etc/environment 2>/dev/null || true
|
|
|
|
append_export_line_for_injection "$key" "$value"
|
|
}
|
|
|
|
mapfile -t arr < <(jq -r 'keys[]' "${JSONSOURCE}")
|
|
|
|
for KEYS in "${arr[@]}"; do
|
|
jtype="$(jq -r --arg k "$KEYS" '.[$k] | type' "$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
|
|
|
|
update_scripts_with_block
|
|
|
|
################
|
|
# Set timezone #
|
|
################
|
|
set +eu
|
|
|
|
if [ -n "$TZ" ] && [ -f /etc/localtime ]; then
|
|
if [ -f /usr/share/zoneinfo/"$TZ" ]; then
|
|
echo "Timezone set from $(cat /etc/timezone) to $TZ"
|
|
ln -snf /usr/share/zoneinfo/"$TZ" /etc/localtime && echo "$TZ" > /etc/timezone
|
|
fi
|
|
fi
|