mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-08-20 11:57:19 +02:00
fix: wait for the Supervisor API before running add-on startup scripts (#2967)
* fix: wait for the Supervisor API before running startup scripts 48 add-ons build their nginx ingress config out of bashio::addon.ip_address and bashio::addon.ingress_port. Both come from one GET /addons/self/info, and when that is answered before the Supervisor is ready bashio prints nothing. Nine add-ons paste the result straight into a sed and end up writing "listen : default_server;", which nginx rejects with `invalid port in ":"`; the other 39 assign first and abort under set -e, leaving %%port%% placeholders. Either way ingress is dead for that boot. ha_entrypoint.sh now polls /addons/self/info once before the cont-init loop and waits until it reports this add-on's ip_address (and, for ingress add-ons, a non-zero ingress_port). Bounded at 30s via HA_SUPERVISOR_WAIT, never fatal, and skipped entirely without SUPERVISOR_TOKEN or curl. When the Supervisor is already up -- the normal case -- it costs one request. qBittorrent is bumped so the change is actually built and reaches the add-on with the open report; the other add-ons pick it up on their next rebuild. Refs #2949, #2962 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: harden the Supervisor wait after bot review - HA_SUPERVISOR_WAIT=08 was accepted by test -gt but read as octal by arithmetic expansion, leaving deadline empty; the comparison then errored every iteration and the loop never exited, hanging start-up. Digits-only validation plus base-10 forcing. - A request started near the deadline could run --max-time past it. The per-request timeout is now capped to the time remaining, and the retry sleep is skipped once the budget is gone, so the ceiling is exact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: probe the Supervisor through bashio instead of curl + sed The wait reimplemented what the 48 consumers already do: it called /addons/self/info with curl and picked the fields out with sed. That parallel implementation was where one of the review findings landed, and it left a residual race -- proving the API answered a moment ago says nothing about the bashio call that runs next. Probing through bashio removes both. bashio caches a successful /addons/self/info under ${CACHE_DIR:-/tmp/.bashio}, so once the probe returns, every bashio::addon.* call in every cont-init script reads that file rather than asking the Supervisor again. Verified: one bashio::addon.ip_address call writes a 26 KB addons.self.info.cache. One call also settles all the fields, so the separate ingress/ingress_port branch was redundant and is gone: a populated ip_address means the whole object is cached. 36 -> 31 code lines. Two consequences handled: bashio's own curl carries no --max-time (api.sh:41), so each attempt is bounded with timeout; and bashio-standalone.sh answers these calls from environment variables without ever contacting the Supervisor, so BASHIO_LIB_FULL gates the probe to images carrying the real library. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -173,9 +173,13 @@ fi
|
||||
####################################
|
||||
|
||||
BASHIO_LIB=""
|
||||
BASHIO_LIB_FULL=false
|
||||
for f in /usr/lib/bashio/bashio.sh /usr/lib/bashio/lib.sh /usr/src/bashio/bashio.sh /usr/local/lib/bashio/bashio.sh; do
|
||||
if [ -f "$f" ]; then
|
||||
BASHIO_LIB="$f"
|
||||
# The real library, which talks to the Supervisor. The standalone shim below only reads
|
||||
# environment variables, which matters to wait_for_supervisor().
|
||||
BASHIO_LIB_FULL=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
@@ -188,6 +192,83 @@ if [ -z "$BASHIO_LIB" ]; then
|
||||
done
|
||||
fi
|
||||
|
||||
##############################
|
||||
# Wait for the Supervisor API #
|
||||
##############################
|
||||
|
||||
# Many cont-init scripts build their nginx ingress config out of bashio::addon.ip_address and
|
||||
# bashio::addon.ingress_port. Both come from one GET /addons/self/info, and when that is answered
|
||||
# before the Supervisor is ready bashio prints nothing: the add-on then either writes
|
||||
# "listen : default_server;" -- which nginx rejects with `invalid port in ":"` -- or aborts under
|
||||
# set -e and leaves the %%port%% placeholders in place. Either way the add-on cannot serve ingress.
|
||||
# Ask for the same values here, through the same bashio calls, until they come back usable --
|
||||
# rather than making 48 add-ons defend themselves against the same empty answer.
|
||||
#
|
||||
# Going through bashio rather than curl is what makes this reliable rather than merely likely:
|
||||
# bashio caches a successful /addons/self/info under ${CACHE_DIR:-/tmp/.bashio}, so once this
|
||||
# returns, every later bashio::addon.* call in every cont-init script reads that file instead of
|
||||
# asking the Supervisor again. A probe that only proved the API was up a moment ago would leave
|
||||
# the very next call free to fail.
|
||||
#
|
||||
# Bounded and never fatal: an add-on with no SUPERVISOR_TOKEN, or a Supervisor that stays
|
||||
# unreachable, still has to start. HA_SUPERVISOR_WAIT (seconds, default 30) sets the ceiling; 0
|
||||
# skips the wait. When the Supervisor is already up -- the normal case -- this costs one request.
|
||||
|
||||
wait_for_supervisor() {
|
||||
local max="${HA_SUPERVISOR_WAIT:-30}"
|
||||
local started deadline remaining attempt announced=0
|
||||
|
||||
# Nothing to wait for without a token. The standalone shim is excluded too: it answers these
|
||||
# calls from environment variables and never contacts the Supervisor, so it can never satisfy
|
||||
# the probe and would burn the whole ceiling on every boot.
|
||||
[ -n "${SUPERVISOR_TOKEN:-}" ] || return 0
|
||||
[ "${BASHIO_LIB_FULL:-false}" = "true" ] || return 0
|
||||
# bashio's own curl carries no --max-time, so each attempt is bounded from the outside.
|
||||
command -v timeout >/dev/null 2>&1 || return 0
|
||||
# Digits only, then forced to base 10: `test -gt` accepts a zero-padded override like 08, but
|
||||
# arithmetic expansion reads it as octal and fails, which would leave the deadline empty and
|
||||
# spin the loop below forever.
|
||||
case "$max" in '' | *[!0-9]*) return 0 ;; esac
|
||||
max=$((10#$max))
|
||||
[ "$max" -gt 0 ] || return 0
|
||||
|
||||
started=$SECONDS
|
||||
deadline=$((started + max))
|
||||
|
||||
while :; do
|
||||
remaining=$((deadline - SECONDS))
|
||||
if [ "$remaining" -le 0 ]; then
|
||||
echo -e "\e[38;5;214m$(date) WARNING: Supervisor API did not report this add-on's network details within ${max}s, continuing anyway\e[0m"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# No single attempt may outlive the ceiling it is bounded by.
|
||||
attempt=5
|
||||
[ "$remaining" -lt "$attempt" ] && attempt="$remaining"
|
||||
|
||||
# One call is enough to settle all of them: bashio fetches the whole /addons/self/info object
|
||||
# and caches it, so a populated ip_address means ingress_port and the rest are cached too.
|
||||
# Run in a child shell so bashio's globals and traps stay out of the entrypoint; its own error
|
||||
# logging is dropped because a failed attempt here is expected, not news.
|
||||
# shellcheck disable=SC2016
|
||||
if timeout "$attempt" bash -c '. "$1" && [ -n "$(bashio::addon.ip_address)" ]' \
|
||||
_ "$BASHIO_LIB" >/dev/null 2>&1; then
|
||||
[ "$announced" -eq 0 ] || echo "Supervisor API ready after $((SECONDS - started))s"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$announced" -eq 0 ]; then
|
||||
echo "Waiting for the Supervisor API to report this add-on's network details..."
|
||||
announced=1
|
||||
fi
|
||||
|
||||
# Skipped when the attempt already consumed what was left, so the sleep cannot overshoot.
|
||||
[ "$((deadline - SECONDS))" -gt 0 ] && sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
wait_for_supervisor
|
||||
|
||||
####################
|
||||
# Starting scripts #
|
||||
####################
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
|
||||
## 5.2.3.2 (2026-08-12)
|
||||
|
||||
- Fix : ingress could fail permanently with `nginx: [emerg] invalid port in ":"`. `30-nginx.sh` pastes `bashio::addon.ip_address` and `bashio::addon.ingress_port` straight into the nginx config; when the Supervisor answers before it is ready both come back empty and the config gets `listen : default_server;`. The add-on entrypoint now waits (up to 30s, tunable with `HA_SUPERVISOR_WAIT`) for the Supervisor to report the add-on's network details before any startup script runs
|
||||
|
||||
## 5.2.3-1 (2026-07-09)
|
||||
|
||||
- Rebuild images after VueTorrent download path fix
|
||||
|
||||
@@ -143,4 +143,4 @@ schema:
|
||||
slug: qbittorrent
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "5.2.3.1"
|
||||
version: "5.2.3.2"
|
||||
|
||||
Reference in New Issue
Block a user