69 lines
3.0 KiB
Bash
Executable File
69 lines
3.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# jarvis-email-watch-liveness.sh — T40 timer-owner liveness check (code-be-01).
|
|
#
|
|
# Owner: code-be-01 (Jason ruling, board T40; jarvis remains author of the
|
|
# watcher code). Detection is SYSTEMD-STATE-ONLY — no pane scraping, no fixed
|
|
# interval assumptions beyond the unit's own 30-min cadence.
|
|
#
|
|
# DEAD conditions (any one pages the owner):
|
|
# 1. timer unit not found or not active
|
|
# 2. NextElapse is infinity (timer will never fire again)
|
|
# 3. service last result failed
|
|
# 4. last service run older than 90 min (3 missed 30-min slots)
|
|
#
|
|
# Exit: 0 = alive (prints state line) · 1 = DEAD (prints reason) · 2 = cannot
|
|
# assert (systemd unreachable — surface, don't guess).
|
|
set -uo pipefail
|
|
|
|
UNIT_TIMER="jarvis-email-watch.timer"
|
|
UNIT_SERVICE="jarvis-email-watch.service"
|
|
CADENCE_MIN=30
|
|
MISSED_SLOTS=3
|
|
STALE_SEC=$(( CADENCE_MIN * 60 * MISSED_SLOTS ))
|
|
|
|
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
|
|
|
active="$(systemctl --user show "$UNIT_TIMER" --property=ActiveState --value 2>/dev/null)"
|
|
rc=$?
|
|
if [ $rc -ne 0 ] || [ -z "$active" ]; then
|
|
echo "DEAD(reason=systemd-unreachable-or-unit-missing unit=$UNIT_TIMER rc=$rc)"
|
|
exit 2
|
|
fi
|
|
|
|
[ "$active" = "active" ] || { echo "DEAD(reason=timer-not-active state=$active)"; exit 1; }
|
|
|
|
# This timer is MONOTONIC-ONLY (OnBootSec/OnUnitActiveSec/OnUnitInactiveSec — no
|
|
# realtime calendar), so NextElapseUSecRealtime is legitimately empty. The
|
|
# unit file's own comment records the failure signature that killed brain-sync
|
|
# for five days: monotonic timer + reboot => NextElapse becomes 'infinity'
|
|
# while is-active/is-enabled stay green. Probe the monotonic property; then
|
|
# take the AUTHORITATIVE next-fire from list-timers (it computes the min over
|
|
# all timer bases; `show`'s aggregate includes the long-expired OnBootSec base
|
|
# and reads as ~never on a healthy timer).
|
|
mono="$(systemctl --user show "$UNIT_TIMER" --property=NextElapseUSecMonotonic --value 2>/dev/null)"
|
|
[ "$mono" != "infinity" ] || { echo "DEAD(reason=next-elapse-infinity monotonic — reboot-killed-timer signature, see unit-file note)"; exit 1; }
|
|
|
|
row="$(systemctl --user list-timers "$UNIT_TIMER" --no-pager 2>/dev/null | grep "$UNIT_TIMER")"
|
|
[ -n "$row" ] || { echo "DEAD(reason=no-list-timers-row unit=$UNIT_TIMER)"; exit 1; }
|
|
case "$row" in
|
|
*"n/a"*) echo "DEAD(reason=list-timers-next-na)"; exit 1 ;;
|
|
esac
|
|
next="$row"
|
|
|
|
result="$(systemctl --user show "$UNIT_SERVICE" --property=Result --value 2>/dev/null)"
|
|
[ "$result" = "success" ] || [ "$result" = "" ] || { echo "DEAD(reason=service-result-failed result=$result)"; exit 1; }
|
|
|
|
execstamp="$(systemctl --user show "$UNIT_SERVICE" --property=ExecMainExitTimestamp --value 2>/dev/null)"
|
|
if [ -n "$execstamp" ] && [ "$execstamp" != "0" ]; then
|
|
last_ep="$(date -d "$execstamp" +%s 2>/dev/null || echo 0)"
|
|
now_ep="$(date +%s)"
|
|
age=$(( now_ep - last_ep ))
|
|
if [ "$age" -gt "$STALE_SEC" ]; then
|
|
echo "DEAD(reason=stale-last-run age_min=$((age/60)) threshold_min=$((STALE_SEC/60)))"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo "ALIVE(active=$active next=$next result=$result last_exit=$execstamp)"
|
|
exit 0
|