diff --git a/.templates/bashio-standalone.sh b/.templates/bashio-standalone.sh index b6defa75a..cddf31304 100644 --- a/.templates/bashio-standalone.sh +++ b/.templates/bashio-standalone.sh @@ -251,4 +251,172 @@ bashio::services() { printf '%s' "${v:-}" } +# ----- extras for broader compatibility -------------------------------------- + +# Simple cache (used by add-ons & bashio itself) +_BASHIO_CACHE_DIR="${BASHIO_CACHE_DIR:-/tmp/.bashio}" +mkdir -p "$_BASHIO_CACHE_DIR" + +bashio::cache.exists() { [ -f "$_BASHIO_CACHE_DIR/${1}.cache" ]; } +bashio::cache.get() { [ -f "$_BASHIO_CACHE_DIR/${1}.cache" ] && cat "$_BASHIO_CACHE_DIR/${1}.cache"; } +bashio::cache.set() { mkdir -p "$_BASHIO_CACHE_DIR"; printf '%s' "${2:-}" > "$_BASHIO_CACHE_DIR/${1}.cache"; } + +# Filesystem helpers frequently used +bashio::fs.file_exists() { [ -f "$1" ]; } +bashio::fs.directory_exists() { [ -d "$1" ]; } # already defined earlier; keep if present +bashio::fs.file_contains() { local f="$1" p="$2"; [ -f "$f" ] && grep -q -- "$p" "$f"; } + +# jq wrapper (some add-ons call bashio::jq) +bashio::jq() { command -v jq >/dev/null 2>&1 && jq "$@"; } + +# env presence (even if empty) used by config.exists +_bashio_env_has() { + local key="$1" p v name + [ -z "$key" ] && return 1 + local variants=( + "$key" + "$(printf '%s' "$key" | tr '.' '_' )" + "$(printf '%s' "$key" | tr '.' '_' | tr '[:lower:]' '[:upper:]')" + "$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')" + ) + for v in "${variants[@]}"; do + for p in "" "CFG_" "CONFIG_" "ADDON_" "OPTION_" "OPT_"; do + name="${p}${v}" + if [ -n "${!name+x}" ]; then # defined, even if empty + printf '%s' "$name" + return 0 + fi + done + done + return 1 +} + +# config.exists : key is present (env or JSON), even if value is empty +bashio::config.exists() { + local key="$1" file="${STANDALONE_OPTIONS_JSON:-}" + _bashio_env_has "$key" && return 0 + if [ -n "$file" ] && command -v jq >/dev/null 2>&1; then + jq -e --arg k "$key" 'haspath(($k|split(".")))' "$file" >/dev/null 2>&1 + return $? + fi + return 1 +} + +# addon.option : write/delete option in JSON when possible; fallback no-op/env +bashio::addon.option() { + local key="$1" value="${2-__BASHIO_UNSET__}" file="${STANDALONE_OPTIONS_JSON:-}" + if [ -n "$file" ] && command -v jq >/dev/null 2>&1; then + local tmp; tmp="$(mktemp)" + if [ "$value" = "__BASHIO_UNSET__" ]; then + jq --arg k "$key" 'delpath(($k|split(".")))' "$file" >"$tmp" && mv "$tmp" "$file" + else + jq --arg k "$key" --arg v "$value" 'setpath(($k|split(".")); $v)' "$file" >"$tmp" && mv "$tmp" "$file" + fi + return 0 + fi + # Fallbacks: export as env or treat delete as no-op + if [ "$value" != "__BASHIO_UNSET__" ]; then + export "$(printf '%s' "$key" | tr '.' '_' | tr '-' '_')"="$value" + fi +} + +# services.available : check if we can resolve at least a host for the service +bashio::services.available() { + local svc="$1" host; host="$(bashio::services "$svc" "host")" + [ -n "$host" ] +} + +# var helpers +bashio::var.false() { ! _bashio_is_true "${1:-}"; } +bashio::var.has_value() { [ -n "${1:-}" ]; } # already present; keep if defined + +# exits used by many add-ons +bashio::exit.ok() { exit 0; } +bashio::exit.nok() { local m="${1:-}"; [ -n "$m" ] && bashio::log.red "$m"; exit 1; } + +# core.check : Supervisor does a config check; allow an overridable command +# Set STANDALONE_CORE_CHECK_CMD="hass --script check_config -c /config" to enable +bashio::core.check() { + if [ -n "${STANDALONE_CORE_CHECK_CMD:-}" ]; then + eval "$STANDALONE_CORE_CHECK_CMD" + else + return 0 + fi +} + +# --- improvements & extra shims --------------------------------------------- + +# Respect NO_COLOR and dumb terminals +if [ -n "${NO_COLOR:-}" ] || [ "${TERM:-}" = "dumb" ]; then + _BASHIO_COLOR=0 +fi + +# net.wait_for: prefer nc if available, fallback to /dev/tcp +_bashio_tcp_wait_nc() { + # $1=host $2=port $3=timeout(s) + command -v nc >/dev/null 2>&1 || return 1 + local host="$1" port="$2" to="${3:-30}" + # BusyBox and OpenBSD nc differ; cover both styles + nc -z -w "$to" "$host" "$port" 2>/dev/null || nc -z "$host" "$port" 2>/dev/null +} +bashio::net.wait_for() { + local host="$1" port="$2" to="${3:-30}" + _bashio_tcp_wait_nc "$host" "$port" "$to" && return 0 + _bashio_tcp_wait "$host" "$port" "$to" +} + +# DNS helper: bashio::dns.host -> prints an IP (or empty) +bashio::dns.host() { + local h="${1:-}" + [ -z "$h" ] && return 1 + if command -v getent >/dev/null 2>&1; then + getent ahostsv4 "$h" | awk '{print $1; exit}' + else + # fallback: try busybox nslookup + nslookup "$h" 2>/dev/null | awk '/^Address: /{print $2; exit}' + fi +} + +# Hostname +bashio::host.hostname() { + command -v hostname >/dev/null 2>&1 && hostname || printf '%s' "${HOSTNAME:-unknown}" +} + +# Home Assistant token (no Supervisor; read from env or JSON) +bashio::homeassistant.token() { + local t="${HOMEASSISTANT_TOKEN:-${HASS_TOKEN:-}}" + if [ -z "$t" ] && [ -n "${STANDALONE_OPTIONS_JSON:-}" ] && command -v jq >/dev/null 2>&1; then + t="$(jq -er '.homeassistant.token // empty' "$STANDALONE_OPTIONS_JSON" 2>/dev/null || true)" + fi + printf '%s' "${t:-}" +} + +# config.array: +# Accepts CSV ("a,b,c"), space/newline-separated text, or JSON array ["a","b"]. +# Prints one item per line (common pattern in add-ons: `mapfile -t arr < <(bashio::config.array key)`). +bashio::config.array() { + local key="${1:-}" raw val + raw="$(bashio::config "$key")" + [ -z "$raw" ] && return 0 + + # JSON array? + if command -v jq >/dev/null 2>&1 && printf '%s' "$raw" | jq -e . >/dev/null 2>&1; then + printf '%s' "$raw" | jq -r '.[]' 2>/dev/null && return 0 + fi + + # CSV -> newline + if printf '%s' "$raw" | grep -q ','; then + printf '%s' "$raw" | tr ',' '\n' + return 0 + fi + + # Already space/newline-separated + printf '%s\n' "$raw" +} + +# Optional: common require.* shims (treat as advisory in standalone) +bashio::config.require.username() { :; } +bashio::config.require.password() { :; } +bashio::config.require.port() { :; } + # -------- end ----------------------------------------------------------------