Files
nr-flow-validator/infra/zigbee2mqtt/scripts/kuma-heartbeat.sh
T
david 5c747899f6 zigbee: heartbeat uses live RestartCount to detect crash-loops
Old heartbeat trusted the retained zigbee2mqtt/bridge/state topic, which stays
{"state":"online"} even while the container crash-loops (6900+ restarts
went undetected). New logic: RestartCount increase since last run =
authoritative DOWN signal; container running = secondary guard; bridge state
is informational only (z2m publishes it on transition, not continuously, so
missing in a short window is normal).
2026-08-29 23:07:53 -07:00

47 lines
1.7 KiB
Bash
Executable File

#!/bin/sh
set -u
KUMA_BASE="${KUMA_BASE:-https://up.notsosm.art}"
TOKEN_FILE="/root/nr-flow-validator/infra/.secrets/kuma-push-token"
STATE_FILE="/root/nr-flow-validator/infra/.secrets/.zb-restart-count"
[ -s "$TOKEN_FILE" ] || exit 1
TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")"
reason=""
# 1. Container liveness.
running=$(docker inspect zigbee2mqtt --format '{{.State.Running}}' 2>/dev/null)
[ "$running" = "true" ] || reason="container_running=${running:-unknown}"
# 2. Crash-loop detection: RestartCount climbing since last run.
rc=$(docker inspect zigbee2mqtt --format '{{.RestartCount}}' 2>/dev/null | tr -d '[:space:]')
if [ -n "$rc" ] && [ -f "$STATE_FILE" ]; then
prev=$(tr -d '[:space:]' < "$STATE_FILE")
if [ "${prev:-0}" -lt "$rc" ] 2>/dev/null; then
reason="${reason:+$reason }crashed_recently(restarts=$rc from $prev)"
fi
fi
printf '%s' "${rc:-0}" > "$STATE_FILE"
# 3. Bridge state — informational only. z2m publishes /bridge/state on
# transitions, NOT continuously, so a missing message in a short window
# is expected during steady operation and must not cause a false DOWN.
# The restart-count check above is the authoritative crash-loop signal.
state=$(docker exec mosquitto mosquitto_sub -R -t 'zigbee2mqtt/bridge/state' -C 1 -W 6 2>/dev/null | tr -d '[:space:]')
if [ -z "$reason" ] && [ "$state" != '{"state":"online"}' ]; then
note="bridge_idle_or_down"
fi
if [ -n "$reason" ]; then
status="down"
msg="coordinator: ${reason}"
else
status="up"
msg="coordinator ok${note:+ ($note)}"
fi
curl -fsS -m 10 -o /dev/null --get \
--data-urlencode "status=$status" \
--data-urlencode "msg=$msg" \
"$KUMA_BASE/api/push/$TOKEN"