5f2c8fdcec
Diagnosis aid: when a rule evaluates but doesn't fire, run now prints 'OK: <cmd> = <value> (no change)' so a rule that silently never crosses its threshold (e.g. wrong sensor in the check command) is visible instead of just 'evaluated N rule(s)'.
220 lines
8.4 KiB
Bash
Executable File
220 lines
8.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# POS: system event-trigger — State-based rule monitors; alerts via notify when a check crosses a threshold
|
|
# POS_SUBCMDS: run config list enable disable status
|
|
# POS_FLAGS: --dry-run
|
|
|
|
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
|
source "$(dirname "$0")/../lib/eventer-lib.sh" 2>/dev/null || source "$(dirname "$0")/eventer-lib.sh"
|
|
source "$(dirname "$0")/../lib/notify.sh" 2>/dev/null || source "$(dirname "$0")/notify.sh"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: pos system event-trigger <subcommand> [--dry-run]
|
|
|
|
State-based rule monitors. Each line in event.env is an independent rule:
|
|
|
|
"<message>" if <check-command> <op> <threshold>
|
|
<check-command> <op> <threshold>
|
|
|
|
The check command is run on every pass; when its first numeric output
|
|
crosses the threshold the message is sent via lib/notify.sh (Telegram by
|
|
default). Rules fire on false→true only, plus one recovery message.
|
|
|
|
Subcommands:
|
|
run Evaluate every rule once (the systemd timer calls this)
|
|
config Interactive editor: add / remove <n> / edit <n> / quit
|
|
list Show rules with their live check values
|
|
enable [interval] Install + start the user timer (default 5m; also:
|
|
10m 15m 30m 45m hourly 2h 6h 12h daily weekly, OnCalendar=…)
|
|
disable Stop + remove the timer
|
|
status Timer + rule count
|
|
|
|
Options:
|
|
--dry-run Preview what would fire / be written, without sending
|
|
-h, --help This help
|
|
|
|
Rules file: $CONFIG_FILE (chmod 600)
|
|
|
|
Examples:
|
|
pos system event-trigger list
|
|
pos system event-trigger config
|
|
pos system event-trigger enable 5m
|
|
pos system event-trigger run --dry-run
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
case "${1:-}" in
|
|
-h|--help|"") usage ;;
|
|
esac
|
|
|
|
DRY_RUN=0
|
|
args=()
|
|
for a in "$@"; do
|
|
case "$a" in
|
|
--dry-run) DRY_RUN=1 ;;
|
|
*) args+=("$a") ;;
|
|
esac
|
|
done
|
|
set -- "${args[@]}"
|
|
|
|
[ -f "$CONFIG_FILE" ] || { log "no rules file yet — run 'pos system event-trigger config'"; }
|
|
|
|
# ── run ──────────────────────────────────────────────────────────
|
|
run_rules() {
|
|
eventer_read_rules
|
|
local n fired hash msg line skipped
|
|
n=0
|
|
skipped=0
|
|
for line in "${RULES[@]}"; do
|
|
case "$line" in
|
|
""|\#*) continue ;;
|
|
esac
|
|
eventer_parse_rule "$line" || { warn "skipping unparseable rule: $line"; skipped=$((skipped + 1)); continue; }
|
|
eventer_eval_check || { warn "check produced no number — $RULE_CMD"; skipped=$((skipped + 1)); continue; }
|
|
n=$((n + 1))
|
|
if eventer_compare "$VALUE_NUM" "$RULE_OP" "$RULE_NUM"; then fired=1; else fired=0; fi
|
|
hash="$(eventer_hash "$line")"
|
|
if [ "$fired" -eq 1 ] && ! eventer_state_is_firing "$hash"; then
|
|
msg="$(eventer_alert_msg)"
|
|
if [ "$DRY_RUN" -eq 1 ]; then
|
|
log "(dry-run) TRIGGER: $msg"
|
|
else
|
|
notify_send "$msg"
|
|
eventer_state_set "$hash" 1
|
|
fi
|
|
elif [ "$fired" -eq 0 ] && eventer_state_is_firing "$hash"; then
|
|
msg="$(eventer_recover_msg)"
|
|
if [ "$DRY_RUN" -eq 1 ]; then
|
|
log "(dry-run) RECOVER: $msg"
|
|
else
|
|
notify_send "$msg"
|
|
eventer_state_set "$hash" 0
|
|
fi
|
|
elif [ "$DRY_RUN" -eq 1 ]; then
|
|
log "(dry-run) OK: $RULE_CMD = $VALUE_DISPLAY (no change)"
|
|
else
|
|
log "OK: $RULE_CMD = $VALUE_DISPLAY (no change)"
|
|
fi
|
|
done
|
|
if [ "$skipped" -gt 0 ]; then
|
|
log "evaluated $n rule(s), skipped $skipped (no number / unparseable)"
|
|
else
|
|
log "evaluated $n rule(s)"
|
|
fi
|
|
}
|
|
|
|
# ── list ─────────────────────────────────────────────────────────
|
|
list_rules() {
|
|
eventer_read_rules
|
|
local i=0 line
|
|
for line in "${RULES[@]}"; do
|
|
i=$((i + 1))
|
|
case "$line" in
|
|
""|\#*) printf '%2d (comment/blank)\n' "$i"; continue ;;
|
|
esac
|
|
if eventer_parse_rule "$line"; then
|
|
if eventer_eval_check; then
|
|
printf '%2d [%s %s %s] %s = %s\n' "$i" "$RULE_CMD" "$RULE_OP" "$RULE_THRESH" \
|
|
"${RULE_MSG:+msg: $RULE_MSG; }" "$VALUE_DISPLAY"
|
|
else
|
|
printf '%2d [%s %s %s] (check produced no number)\n' "$i" "$RULE_CMD" "$RULE_OP" "$RULE_THRESH"
|
|
fi
|
|
else
|
|
printf '%2d INVALID — %s\n' "$i" "$line"
|
|
fi
|
|
done
|
|
[ "$i" -gt 0 ] || echo "(no rules — run 'pos system event-trigger config')"
|
|
}
|
|
|
|
# ── config editor ────────────────────────────────────────────────
|
|
config_editor() {
|
|
eventer_read_rules
|
|
local n rule action ok
|
|
while true; do
|
|
echo "── rules in $CONFIG_FILE ──" >&2
|
|
if [ "${#RULES[@]}" -eq 0 ]; then
|
|
echo " (empty)" >&2
|
|
else
|
|
for i in "${!RULES[@]}"; do
|
|
printf ' %2d %s\n' "$((i + 1))" "${RULES[i]}" >&2
|
|
done
|
|
fi
|
|
read -rp "action [add / remove <n> / edit <n> / quit]: " action
|
|
case "$action" in
|
|
quit|q) break ;;
|
|
add)
|
|
read -rp "Rule (\"msg\" if <cmd> <op> <thr>): " rule
|
|
[ -n "$rule" ] || continue
|
|
if ! eventer_parse_rule "$rule"; then
|
|
warn "not a valid rule — need: <command> <op> <threshold> (op: > < >= <= == !=)"
|
|
continue
|
|
fi
|
|
if ! eventer_eval_check; then
|
|
warn "check '$RULE_CMD' produced no number — rule added anyway (it will warn on run)"
|
|
else
|
|
ok "check '$RULE_CMD' currently reports $VALUE_DISPLAY"
|
|
fi
|
|
if grep -Fxq "$rule" "$CONFIG_FILE" 2>/dev/null; then
|
|
warn "rule already present, not added"
|
|
elif [ "$DRY_RUN" -eq 1 ]; then
|
|
log "(dry-run) would add: $rule"
|
|
else
|
|
mkdir -p "$(dirname "$CONFIG_FILE")"
|
|
printf '%s\n' "$rule" >>"$CONFIG_FILE"
|
|
chmod 600 "$CONFIG_FILE"
|
|
eventer_read_rules
|
|
fi
|
|
;;
|
|
remove*)
|
|
n="${action#remove }"
|
|
[[ "$n" =~ ^[0-9]+$ ]] || { warn "use: remove <line-number>"; continue; }
|
|
if [ "$n" -lt 1 ] || [ "$n" -gt "${#RULES[@]}" ]; then
|
|
warn "no line $n"; continue
|
|
fi
|
|
read -rp "remove line $n: '${RULES[$((n - 1))]}'? [y/N]: " ok
|
|
[[ "$ok" =~ ^[Yy] ]] || continue
|
|
if [ "$DRY_RUN" -eq 1 ]; then
|
|
log "(dry-run) would remove line $n"
|
|
else
|
|
unset "RULES[$((n - 1))]"
|
|
RULES=("${RULES[@]}")
|
|
eventer_write_rules
|
|
fi
|
|
;;
|
|
edit*)
|
|
n="${action#edit }"
|
|
[[ "$n" =~ ^[0-9]+$ ]] || { warn "use: edit <line-number>"; continue; }
|
|
if [ "$n" -lt 1 ] || [ "$n" -gt "${#RULES[@]}" ]; then
|
|
warn "no line $n"; continue
|
|
fi
|
|
printf ' current: %s\n' "${RULES[$((n - 1))]}" >&2
|
|
read -rp " new rule (blank = keep): " rule
|
|
[ -n "$rule" ] || continue
|
|
if ! eventer_parse_rule "$rule"; then
|
|
warn "not a valid rule — edit cancelled"
|
|
continue
|
|
fi
|
|
if [ "$DRY_RUN" -eq 1 ]; then
|
|
log "(dry-run) would replace line $n"
|
|
else
|
|
RULES[$((n - 1))]="$rule"
|
|
eventer_write_rules
|
|
fi
|
|
;;
|
|
*) warn "unknown action (add | remove <n> | edit <n> | quit)" ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
case "${1:-}" in
|
|
run) run_rules ;;
|
|
config) config_editor ;;
|
|
list) list_rules ;;
|
|
enable) eventer_enable "${2:-5m}" ;;
|
|
disable) eventer_disable ;;
|
|
status) eventer_status ;;
|
|
*) err "unknown subcommand '${1:-}' (see --help)" ;;
|
|
esac
|