mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-06-26 09:16:07 +02:00
bashio now emits deprecation warnings for bashio::addon.* calls. This migrates all 227 call-sites across 86 files to use the new bashio::app.* API. Cross-compatibility is preserved in two ways: 1. bashio-standalone.sh: adds bashio::app.* functions that forward to bashio::addon.* (used when running without HA Supervisor). Also adds the missing ingress_url, restart and stop stubs. 2. ha_entrypoint.sh: injects a one-liner compat shim after the shebang of every cont-init and service script at container startup. On old bashio installations (bashio::app.* absent) the shim defines bashio::app.* as thin wrappers around bashio::addon.*; on new bashio the guard condition is true so the block is a no-op. The probe script in ha_entrypoint.sh is also updated to try bashio::app.version before falling back to bashio::addon.version. https://claude.ai/code/session_011FWFBhYQ6VS5FauSqv4UMo
461 lines
16 KiB
Bash
Executable File
461 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# /usr/local/lib/bashio-standalone.sh
|
||
# shellcheck shell=bash
|
||
#
|
||
# Minimal bashio compatibility layer for running Home Assistant add-ons
|
||
# in standalone containers (no Supervisor).
|
||
#
|
||
# Goals:
|
||
# - Keep add-ons that depend on bashio from crashing outside HA Supervisor
|
||
# - Prefer ENV, optionally read /data/options.json (jq required)
|
||
# - Provide common bashio::* functions seen across add-ons
|
||
#
|
||
# Usage (typical):
|
||
# if ! bashio::supervisor.ping 2>/dev/null; then
|
||
# # standalone behavior...
|
||
# fi
|
||
# source /usr/local/lib/bashio-standalone.sh
|
||
|
||
set -u
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Defaults
|
||
# -----------------------------------------------------------------------------
|
||
: "${STANDALONE_OPTIONS_JSON:=/data/options.json}"
|
||
: "${BASHIO_CACHE_DIR:=/tmp/.bashio}"
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Color handling
|
||
# -----------------------------------------------------------------------------
|
||
_BASHIO_COLOR=1
|
||
[ ! -t 1 ] && _BASHIO_COLOR=0
|
||
[ -n "${NO_COLOR:-}" ] && _BASHIO_COLOR=0
|
||
[ "${TERM:-}" = "dumb" ] && _BASHIO_COLOR=0
|
||
|
||
_bashio_color() {
|
||
[ "$_BASHIO_COLOR" = "1" ] || return 0
|
||
case "${1:-}" in
|
||
blue) printf '\033[34m' ;;
|
||
green) printf '\033[32m' ;;
|
||
yellow) printf '\033[33m' ;;
|
||
red) printf '\033[31m' ;;
|
||
magenta) printf '\033[35m' ;;
|
||
reset) printf '\033[0m' ;;
|
||
*) printf '' ;;
|
||
esac
|
||
}
|
||
|
||
_bashio_log() {
|
||
local c="${1:-}"; shift || true
|
||
printf '%s%s%s\n' "$(_bashio_color "$c")" "$*" "$(_bashio_color reset)"
|
||
}
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Helpers
|
||
# -----------------------------------------------------------------------------
|
||
_bashio_is_true() {
|
||
case "${1:-}" in
|
||
1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
|
||
*) return 1 ;;
|
||
esac
|
||
}
|
||
|
||
# ENV mapping helper:
|
||
# tries variants + prefixes and prints the value if env var is defined (even empty),
|
||
# returning 0 when found, 1 when not found.
|
||
_bashio_env_get() {
|
||
local key="${1:-}"
|
||
[ -n "$key" ] || return 1
|
||
|
||
local norm norm_uc raw_uc
|
||
norm="$(printf '%s' "$key" | tr '.-' '__')"
|
||
norm_uc="$(printf '%s' "$norm" | tr '[:lower:]' '[:upper:]')"
|
||
raw_uc="$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')"
|
||
|
||
local variants=(
|
||
"$key"
|
||
"$raw_uc"
|
||
"$norm"
|
||
"$norm_uc"
|
||
)
|
||
|
||
local prefixes=("" "CFG_" "CONFIG_" "ADDON_" "OPTION_" "OPT_")
|
||
|
||
local v p name
|
||
for v in "${variants[@]}"; do
|
||
for p in "${prefixes[@]}"; do
|
||
name="${p}${v}"
|
||
if [ -n "${!name+x}" ]; then
|
||
printf '%s' "${!name}"
|
||
return 0
|
||
fi
|
||
done
|
||
done
|
||
|
||
return 1
|
||
}
|
||
|
||
# env presence (even if empty) used by config.exists
|
||
_bashio_env_has() {
|
||
local key="${1:-}"
|
||
[ -n "$key" ] || return 1
|
||
_bashio_env_get "$key" >/dev/null 2>&1
|
||
}
|
||
|
||
# JSON options source (jq required). Prints value or empty; returns 0 always.
|
||
_bashio_json_get() {
|
||
local key="${1:-}"
|
||
local file="${STANDALONE_OPTIONS_JSON:-}"
|
||
|
||
[ -n "$key" ] || return 0
|
||
[ -n "$file" ] || return 0
|
||
[ -f "$file" ] || return 0
|
||
command -v jq >/dev/null 2>&1 || return 0
|
||
|
||
# getpath(split(".")) supports nested access; missing => empty
|
||
jq -er --arg k "$key" 'getpath(($k|split("."))) // empty' "$file" 2>/dev/null || true
|
||
}
|
||
|
||
# Net wait using /dev/tcp with a timeout
|
||
_bashio_tcp_wait() {
|
||
local host="${1:-}" port="${2:-}" to="${3:-30}"
|
||
[ -n "$host" ] && [ -n "$port" ] || return 1
|
||
|
||
local start now
|
||
start="$(date +%s)"
|
||
while :; do
|
||
if exec 3<>"/dev/tcp/${host}/${port}" 2>/dev/null; then
|
||
exec 3>&- 3<&-
|
||
return 0
|
||
fi
|
||
now="$(date +%s)"
|
||
if [ $((now - start)) -ge "$to" ]; then
|
||
return 1
|
||
fi
|
||
sleep 1
|
||
done
|
||
}
|
||
|
||
# Prefer nc if present, fallback to /dev/tcp
|
||
_bashio_tcp_wait_nc() {
|
||
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
|
||
}
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Logging API
|
||
# -----------------------------------------------------------------------------
|
||
bashio::log.blue() { _bashio_log blue "$*"; }
|
||
bashio::log.green() { _bashio_log green "$*"; }
|
||
bashio::log.yellow() { _bashio_log yellow "$*"; }
|
||
bashio::log.red() { _bashio_log red "$*"; }
|
||
bashio::log.magenta() { _bashio_log magenta "$*"; }
|
||
|
||
# Common aliases
|
||
bashio::log.info() { bashio::log.blue "$@"; }
|
||
bashio::log.warning() { bashio::log.yellow "$@"; }
|
||
bashio::log.error() { bashio::log.red "$@"; }
|
||
bashio::log.debug() { printf '%s\n' "$*"; }
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Supervisor shim
|
||
# -----------------------------------------------------------------------------
|
||
bashio::supervisor.ping() {
|
||
_bashio_is_true "${STANDALONE_FORCE_SUPERVISOR_PING:-}" && return 0
|
||
return 1
|
||
}
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Add-on metadata
|
||
# -----------------------------------------------------------------------------
|
||
bashio::addon.name() { printf '%s' "${ADDON_NAME:-Standalone container}"; }
|
||
bashio::addon.description() { printf '%s' "${ADDON_DESCRIPTION:-Running without Home Assistant Supervisor}"; }
|
||
bashio::addon.version() { printf '%s' "${BUILD_VERSION:-1.0}"; }
|
||
bashio::addon.version_latest(){ printf '%s' "${ADDON_VERSION_LATEST:-${BUILD_VERSION:-1.0}}"; }
|
||
|
||
bashio::addon.update_available() {
|
||
if [ -n "${ADDON_VERSION_LATEST:-}" ] && [ "${ADDON_VERSION_LATEST:-}" != "${BUILD_VERSION:-}" ]; then
|
||
printf '%s' "true"
|
||
else
|
||
printf '%s' "false"
|
||
fi
|
||
}
|
||
|
||
bashio::addon.ingress_port() { printf '%s' "${ADDON_INGRESS_PORT:-}"; }
|
||
bashio::addon.ingress_entry() { printf '%s' "${ADDON_INGRESS_ENTRY:-}"; }
|
||
bashio::addon.ip_address() { printf '%s' "${ADDON_IP_ADDRESS:-}"; }
|
||
|
||
# Ports:
|
||
# - numeric arg "8080" -> env PORT_8080 or ADDON_PORT_8080, fallback to the number
|
||
# - non-numeric "WEB_PORT" -> resolve as config/env key
|
||
bashio::addon.port() {
|
||
local arg="${1:-}"
|
||
if [[ "$arg" =~ ^[0-9]+$ ]]; then
|
||
local v=""
|
||
v="$(_bashio_env_get "PORT_${arg}" 2>/dev/null || true)"
|
||
[ -z "$v" ] && v="$(_bashio_env_get "ADDON_PORT_${arg}" 2>/dev/null || true)"
|
||
printf '%s' "${v:-$arg}"
|
||
else
|
||
printf '%s' "$(_bashio_env_get "$arg" 2>/dev/null || true)"
|
||
fi
|
||
}
|
||
|
||
# addon.ingress_url : HA Supervisor provides this; in standalone mode return empty
|
||
bashio::addon.ingress_url() { printf '%s' "${ADDON_INGRESS_URL:-}"; }
|
||
|
||
# addon.restart / addon.stop : no-ops in standalone (no Supervisor to call)
|
||
bashio::addon.restart() { bashio::log.warning "bashio::app.restart called in standalone mode – no-op"; }
|
||
bashio::addon.stop() { bashio::log.warning "bashio::app.stop called in standalone mode – no-op"; }
|
||
|
||
# addon.option : write/delete option in JSON when possible; fallback export env
|
||
bashio::addon.option() {
|
||
local key="${1:-}" value="${2-__BASHIO_UNSET__}" file="${STANDALONE_OPTIONS_JSON:-}"
|
||
[ -n "$key" ] || return 0
|
||
|
||
if [ -n "$file" ] && [ -f "$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
|
||
|
||
# Fallback: export as env (dot/dash -> underscore). Delete becomes no-op.
|
||
if [ "$value" != "__BASHIO_UNSET__" ]; then
|
||
export "$(printf '%s' "$key" | tr '.-' '__')"="$value"
|
||
fi
|
||
}
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# System info
|
||
# -----------------------------------------------------------------------------
|
||
bashio::info.operating_system() {
|
||
if [ -r /etc/os-release ]; then
|
||
# shellcheck disable=SC1091
|
||
. /etc/os-release
|
||
printf '%s' "${PRETTY_NAME:-${NAME:-Linux}}"
|
||
else
|
||
printf '%s' "Linux"
|
||
fi
|
||
}
|
||
bashio::info.arch() { uname -m; }
|
||
bashio::info.machine() { uname -m; }
|
||
bashio::info.homeassistant(){ printf '%s' "standalone"; }
|
||
bashio::info.supervisor() { printf '%s' "standalone"; }
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Config API
|
||
# -----------------------------------------------------------------------------
|
||
bashio::config() {
|
||
local key="${1:-}"
|
||
[ -n "$key" ] || { printf '%s' ""; return 0; }
|
||
|
||
local v=""
|
||
if _bashio_env_get "$key" >/dev/null 2>&1; then
|
||
v="$(_bashio_env_get "$key" 2>/dev/null || true)"
|
||
fi
|
||
[ -z "$v" ] && v="$(_bashio_json_get "$key")"
|
||
printf '%s' "${v:-}"
|
||
}
|
||
|
||
bashio::config.has_value() { [ -n "$(bashio::config "$1")" ]; }
|
||
|
||
bashio::config.true() {
|
||
_bashio_is_true "$(bashio::config "$1")"
|
||
}
|
||
|
||
# config.exists : key is present (env or JSON), even if value is empty
|
||
bashio::config.exists() {
|
||
local key="${1:-}" file="${STANDALONE_OPTIONS_JSON:-}"
|
||
[ -n "$key" ] || return 1
|
||
|
||
if _bashio_env_has "$key"; then
|
||
return 0
|
||
fi
|
||
|
||
if [ -n "$file" ] && [ -f "$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
|
||
}
|
||
|
||
# Common "require.*" shims (advisory/no-op in standalone)
|
||
bashio::config.require.ssl() { printf '%s' "${REQUIRE_SSL:-true}"; }
|
||
bashio::config.require.username() { :; }
|
||
bashio::config.require.password() { :; }
|
||
bashio::config.require.port() { :; }
|
||
|
||
# config.array:
|
||
# Accepts CSV ("a,b,c"), space/newline-separated text, or JSON array ["a","b"].
|
||
# Prints one item per line.
|
||
bashio::config.array() {
|
||
local key="${1:-}" raw
|
||
raw="$(bashio::config "$key")"
|
||
[ -n "$raw" ] || return 0
|
||
|
||
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
|
||
|
||
if printf '%s' "$raw" | grep -q ','; then
|
||
printf '%s' "$raw" | tr ',' '\n'
|
||
return 0
|
||
fi
|
||
|
||
printf '%s\n' "$raw"
|
||
}
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# var helpers
|
||
# -----------------------------------------------------------------------------
|
||
bashio::var.true() { _bashio_is_true "${1:-}"; }
|
||
bashio::var.false() { ! _bashio_is_true "${1:-}"; }
|
||
bashio::var.has_value() { [ -n "${1:-}" ]; }
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Filesystem helpers
|
||
# -----------------------------------------------------------------------------
|
||
bashio::fs.file_exists() { [ -f "${1:-}" ]; }
|
||
bashio::fs.directory_exists() { [ -d "${1:-}" ]; }
|
||
bashio::fs.file_contains() {
|
||
local f="${1:-}" p="${2:-}"
|
||
[ -f "$f" ] && grep -q -- "$p" "$f" 2>/dev/null
|
||
}
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Network helpers
|
||
# -----------------------------------------------------------------------------
|
||
# Wait for TCP service: bashio::net.wait_for host port [timeout]
|
||
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 <hostname> -> prints an IP (or empty)
|
||
bashio::dns.host() {
|
||
local h="${1:-}"
|
||
[ -n "$h" ] || return 1
|
||
if command -v getent >/dev/null 2>&1; then
|
||
getent ahostsv4 "$h" | awk '{print $1; exit}'
|
||
else
|
||
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}"
|
||
}
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Services discovery shim
|
||
# -----------------------------------------------------------------------------
|
||
# Usage:
|
||
# bashio::services "mqtt" "host"
|
||
# bashio::services.available "mqtt"
|
||
bashio::services() {
|
||
local svc="${1:-}" key="${2:-}"
|
||
[ -n "$svc" ] && [ -n "$key" ] || { printf '%s' ""; return 0; }
|
||
|
||
local upper svc_upper var v=""
|
||
upper="$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')"
|
||
svc_upper="$(printf '%s' "$svc" | tr '[:lower:]' '[:upper:]')"
|
||
|
||
# Common mappings
|
||
case "$svc_upper:$upper" in
|
||
MQTT:HOST) var="MQTT_HOST" ;;
|
||
MQTT:PORT) var="MQTT_PORT" ;;
|
||
MQTT:USERNAME) var="MQTT_USER" ;;
|
||
MQTT:PASSWORD) var="MQTT_PASSWORD" ;;
|
||
MQTT:TLS) var="MQTT_TLS" ;;
|
||
MYSQL:HOST|MARIADB:HOST) var="DB_HOST" ;;
|
||
MYSQL:PORT|MARIADB:PORT) var="DB_PORT" ;;
|
||
MYSQL:USERNAME|MARIADB:USERNAME) var="DB_USER" ;;
|
||
MYSQL:PASSWORD|MARIADB:PASSWORD) var="DB_PASSWORD" ;;
|
||
MYSQL:DATABASE|MARIADB:DATABASE) var="DB_NAME" ;;
|
||
*) var="${svc_upper}_${upper}" ;;
|
||
esac
|
||
|
||
v="$(_bashio_env_get "$var" 2>/dev/null || true)"
|
||
if [ -z "$v" ]; then
|
||
v="$(_bashio_json_get "services.${svc}.${key}")"
|
||
[ -z "$v" ] && v="$(_bashio_json_get "${svc}.${key}")"
|
||
fi
|
||
printf '%s' "${v:-}"
|
||
}
|
||
|
||
bashio::services.available() {
|
||
local svc="${1:-}" host
|
||
host="$(bashio::services "$svc" "host")"
|
||
[ -n "$host" ]
|
||
}
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Cache
|
||
# -----------------------------------------------------------------------------
|
||
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() { printf '%s' "${2:-}" > "$BASHIO_CACHE_DIR/${1}.cache"; }
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# jq wrapper (some add-ons call bashio::jq)
|
||
# -----------------------------------------------------------------------------
|
||
bashio::jq() { command -v jq >/dev/null 2>&1 && jq "$@"; }
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Home Assistant token
|
||
# -----------------------------------------------------------------------------
|
||
bashio::homeassistant.token() {
|
||
local t="${HOMEASSISTANT_TOKEN:-${HASS_TOKEN:-}}"
|
||
if [ -z "$t" ] && [ -n "${STANDALONE_OPTIONS_JSON:-}" ] && [ -f "${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:-}"
|
||
}
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Exit helpers
|
||
# -----------------------------------------------------------------------------
|
||
bashio::exit.ok() { exit 0; }
|
||
bashio::exit.nok() { local m="${1:-}"; [ -n "$m" ] && bashio::log.red "$m"; exit 1; }
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Core config check shim
|
||
# -----------------------------------------------------------------------------
|
||
# 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
|
||
}
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# bashio::app.* — forward-compat aliases for the new API (replaces addon.*)
|
||
# Scripts should use bashio::app.* going forward; bashio::addon.* is kept for
|
||
# backward compatibility with older bashio installations.
|
||
# -----------------------------------------------------------------------------
|
||
bashio::app.name() { bashio::addon.name "$@"; }
|
||
bashio::app.description() { bashio::addon.description "$@"; }
|
||
bashio::app.version() { bashio::addon.version "$@"; }
|
||
bashio::app.version_latest() { bashio::addon.version_latest "$@"; }
|
||
bashio::app.update_available(){ bashio::addon.update_available "$@"; }
|
||
bashio::app.ingress_port() { bashio::addon.ingress_port "$@"; }
|
||
bashio::app.ingress_entry() { bashio::addon.ingress_entry "$@"; }
|
||
bashio::app.ingress_url() { bashio::addon.ingress_url "$@"; }
|
||
bashio::app.ip_address() { bashio::addon.ip_address "$@"; }
|
||
bashio::app.port() { bashio::addon.port "$@"; }
|
||
bashio::app.option() { bashio::addon.option "$@"; }
|
||
bashio::app.restart() { bashio::addon.restart "$@"; }
|
||
bashio::app.stop() { bashio::addon.stop "$@"; }
|