mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-08-04 12:20:33 +02:00
Three minimum-scope robustness fixes in .templates/00-global_var.sh: 1. shell_quote no longer doubles backslashes inside single quotes (e.g. 'C:\Users\test' was producing 'C:\\Users\\test' in injected scripts) and now uses the canonical '\'' escape for single quotes instead of the buggy '"'"' + trailing-space-trim form, which was mangling values containing apostrophes (e.g. "it's" -> "it' s"). 2. /.env and /etc/environment are now patched in place: existing content from the upstream image and the system (PATH, LANG, etc.) is preserved and only the marked ADDON ENV block is replaced. Previously the whole files were overwritten, destroying upstream defaults. 3. Writing to /var/run/s6/container_environment is now best-effort (|| true) so a read-only mount on a single entry cannot abort the rest of the env injection under set -e.
268 lines
8.7 KiB
Bash
Executable File
268 lines
8.7 KiB
Bash
Executable File
#!/usr/bin/with-contenv bashio
|
|
# shellcheck shell=bash
|
|
|
|
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 "Converting addon options to environment variables"
|
|
bashio::log.notice "Supports custom env_vars"
|
|
bashio::log.notice "https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2"
|
|
echo ""
|
|
|
|
################################################################################
|
|
# Inputs
|
|
################################################################################
|
|
JSONSOURCE="/data/options.json"
|
|
ENV_FILE="/.env"
|
|
ETC_ENV_FILE="/etc/environment"
|
|
|
|
[[ -f "$JSONSOURCE" ]] || bashio::exit.nok "Missing $JSONSOURCE"
|
|
command -v jq >/dev/null || bashio::exit.nok "jq is required"
|
|
|
|
mkdir -p /etc
|
|
touch "$ETC_ENV_FILE"
|
|
|
|
################################################################################
|
|
# Temp helper
|
|
################################################################################
|
|
mktemp_safe() {
|
|
local tmpdir="${TMPDIR:-/tmp}"
|
|
mkdir -p "$tmpdir"
|
|
mktemp "$tmpdir/tmp.XXXXXXXXXX"
|
|
}
|
|
|
|
################################################################################
|
|
# Secrets support
|
|
################################################################################
|
|
SECRETSOURCE=""
|
|
[[ -f /homeassistant/secrets.yaml ]] && SECRETSOURCE="/homeassistant/secrets.yaml"
|
|
[[ -z "$SECRETSOURCE" && -f /config/secrets.yaml ]] && SECRETSOURCE="/config/secrets.yaml"
|
|
|
|
resolve_secret() {
|
|
local v="$1" name line
|
|
[[ "$v" =~ ^[[:space:]]*\!secret[[:space:]]+(.+)$ ]] || { printf '%s' "$v"; return; }
|
|
name="${BASH_REMATCH[1]}"
|
|
[[ -n "$SECRETSOURCE" ]] || bashio::exit.nok "Secrets not mounted"
|
|
|
|
# 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"
|
|
)"
|
|
|
|
[[ -n "$line" ]] || bashio::exit.nok "Secret $name not found"
|
|
printf '%s' "$line"
|
|
}
|
|
|
|
################################################################################
|
|
# Quoting
|
|
################################################################################
|
|
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"
|
|
}
|
|
|
|
shell_quote() {
|
|
# Single-quote for safe injection in shell code.
|
|
# Inside single quotes only the single quote needs escaping ('->'\'').
|
|
# Do NOT escape backslashes: they are literal inside single quotes.
|
|
local s="${1//\'/\'\\\'\'}"
|
|
printf "'%s'" "$s"
|
|
}
|
|
|
|
################################################################################
|
|
# S6 + script injection block
|
|
################################################################################
|
|
BLOCK_BEGIN="# --- BEGIN ADDON ENV (generated) ---"
|
|
BLOCK_END="# --- END ADDON ENV (generated) ---"
|
|
|
|
EXPORT_BLOCK="$(mktemp_safe)"
|
|
KV_FILE="$(mktemp_safe)"
|
|
trap 'rm -f "$EXPORT_BLOCK" "$KV_FILE"' EXIT
|
|
|
|
{
|
|
echo "$BLOCK_BEGIN"
|
|
echo "# Generated from $JSONSOURCE"
|
|
echo "$BLOCK_END"
|
|
} > "$EXPORT_BLOCK"
|
|
|
|
append_export() {
|
|
local k="$1" v="$2" q
|
|
q="$(shell_quote "$v")"
|
|
|
|
awk -v k="$k" -v q="$q" -v e="$BLOCK_END" '
|
|
$0==e { print "export " k "=" q }
|
|
{ print }
|
|
' "$EXPORT_BLOCK" > "$EXPORT_BLOCK.tmp"
|
|
mv "$EXPORT_BLOCK.tmp" "$EXPORT_BLOCK"
|
|
}
|
|
|
|
inject_block() {
|
|
local f="$1" tmp
|
|
tmp="$(mktemp_safe)"
|
|
|
|
awk -v b="$BLOCK_BEGIN" -v e="$BLOCK_END" -v bf="$EXPORT_BLOCK" '
|
|
function emit(){ while((getline l<bf)>0) print l; close(bf) }
|
|
BEGIN{ skip=0; injected=0 }
|
|
{
|
|
if($0==b){ skip=1; if(!injected){ emit(); injected=1 } next }
|
|
if($0==e){ skip=0; next }
|
|
if(skip) next
|
|
|
|
if(NR==1 && $0~/^#!/){
|
|
print
|
|
if(!injected){ emit(); injected=1 }
|
|
next
|
|
}
|
|
print
|
|
}
|
|
END{ if(!injected) emit() }
|
|
' "$f" > "$tmp"
|
|
|
|
cat "$tmp" > "$f"
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
################################################################################
|
|
# Export handler
|
|
################################################################################
|
|
export_var() {
|
|
local k="$1" v="$2"
|
|
|
|
# Valid env var identifier only
|
|
[[ "$k" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || {
|
|
bashio::log.warning "Skipping invalid env var name: $k"
|
|
return 0
|
|
}
|
|
|
|
v="$(resolve_secret "$v")"
|
|
|
|
if [[ "${k,,}" =~ pass|token|secret|apikey|api_key|private|pwd|key ]]; then
|
|
bashio::log.blue "$k=******"
|
|
else
|
|
bashio::log.blue "$k=$v"
|
|
fi
|
|
|
|
# Runtime environment (no eval, safe for special chars)
|
|
export "$k=$v"
|
|
|
|
# S6 environment (preferred for services); best-effort, tolerate RO mounts
|
|
if [[ -d /var/run/s6/container_environment ]]; then
|
|
printf '%s' "$v" > "/var/run/s6/container_environment/$k" || true
|
|
fi
|
|
|
|
# Queue for .env and /etc/environment (written once, idempotent)
|
|
echo "$k=$(dotenv_quote "$v")" >> "$KV_FILE"
|
|
|
|
# Add to injected export block for scripts
|
|
append_export "$k" "$v"
|
|
}
|
|
|
|
################################################################################
|
|
# JSON parsing (jq bug fixed: use "? //", not "?//")
|
|
# Order: 1) top-level addon options first, 2) env_vars second (can override)
|
|
################################################################################
|
|
while IFS= read -r -d $'\0' k && IFS= read -r v; do
|
|
export_var "$k" "$v"
|
|
done < <(
|
|
jq -r '
|
|
def emit(k; v): "\((k|tostring))\u0000\((v|tostring))";
|
|
|
|
. as $root
|
|
| (
|
|
# 1) top-level scalar options excluding env_vars (processed first)
|
|
$root
|
|
| to_entries[]
|
|
| select(.key != "env_vars")
|
|
| select((.value|type) != "object" and (.value|type) != "array" and (.value|type) != "null")
|
|
| emit(.key; .value)
|
|
),
|
|
(
|
|
# 2) env_vars[] (processed second, overrides addon options if same key)
|
|
($root.env_vars? // [])[] as $e
|
|
| if ($e|type) == "object" then
|
|
if ($e|has("name") and has("value")) then
|
|
emit($e.name; ($e.value // ""))
|
|
else
|
|
$e|to_entries[]|emit(.key; (.value // ""))
|
|
end
|
|
else
|
|
# string "KEY=VALUE" form (value may contain '=')
|
|
($e|tostring) as $s
|
|
| if ($s|test("^[^=]+=")) then
|
|
($s|capture("^(?<k>[^=]+)=(?<v>.*)$")) as $m
|
|
| emit($m.k; $m.v)
|
|
else empty end
|
|
end
|
|
)
|
|
' "$JSONSOURCE"
|
|
)
|
|
|
|
################################################################################
|
|
# Write .env and /etc/environment (idempotent: replace only our block,
|
|
# preserving any pre-existing content from the upstream image / system).
|
|
# Notes:
|
|
# - /.env is commonly used by apps expecting dotenv format
|
|
# - /etc/environment is read by PAM/system tooling; KEY="value" is acceptable
|
|
################################################################################
|
|
update_env_file() {
|
|
local f="$1" tmp
|
|
tmp="$(mktemp_safe)"
|
|
if [[ -f "$f" ]]; then
|
|
awk -v b="$BLOCK_BEGIN" -v e="$BLOCK_END" '
|
|
$0==b { skip=1; next }
|
|
$0==e { skip=0; next }
|
|
!skip
|
|
' "$f" > "$tmp"
|
|
fi
|
|
{
|
|
echo "$BLOCK_BEGIN"
|
|
cat "$KV_FILE"
|
|
echo "$BLOCK_END"
|
|
} >> "$tmp"
|
|
mv "$tmp" "$f"
|
|
}
|
|
update_env_file "$ENV_FILE"
|
|
update_env_file "$ETC_ENV_FILE"
|
|
|
|
################################################################################
|
|
# Inject into scripts and shells (best-effort)
|
|
################################################################################
|
|
for f in /etc/services.d/*/run /etc/s6-overlay/s6-rc.d/*/run /etc/cont-init.d/*.sh /entrypoint.sh /etc/bash.bashrc "${GLOBAL_VAR_FILES:-}"; do
|
|
[[ -f "$f" ]] && inject_block "$f"
|
|
done
|
|
|
|
if [[ -n "${HOME:-}" ]]; then
|
|
mkdir -p "$HOME"
|
|
touch "$HOME/.bashrc"
|
|
inject_block "$HOME/.bashrc"
|
|
fi
|
|
|
|
################################################################################
|
|
# Timezone (best-effort)
|
|
################################################################################
|
|
set +e
|
|
if [[ -n "$TZ" && -f "/usr/share/zoneinfo/$TZ" ]]; then
|
|
ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime
|
|
echo "$TZ" > /etc/timezone
|
|
fi
|