power / knowledge

upssched로 UPS 이벤트를 Telegram으로 받기

paul 2025.10.07 386words (2m)

UPS 상태가 바뀌면 upsmon 이 이벤트를 던진다. NOTIFYCMD 에 스크립트를 걸면 그때마다 실행된다. 그런데 여기에 알림 스크립트를 직접 걸면 곤란한 게 하나 있다.

순간 정전이다. 전기가 1초 깜빡이면 ONBATT와 ONLINE이 연달아 날아오고 알림도 두 번 온다. 하루에 몇 번씩 이러면 알림을 안 보게 된다.

그래서 NOTIFYCMD 에는 upssched 를 걸고, 실제 알림은 upssched 가 조건을 따져서 부르게 했다.

bash
# upsmon.conf
NOTIFYCMD /usr/sbin/upssched

upsmon.confNOTIFYFLAGEXEC 가 들어 있어야 NOTIFYCMD 가 불린다.

bash
NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC
NOTIFYFLAG ONLINE SYSLOG+WALL+EXEC
NOTIFYFLAG LOWBATT SYSLOG+WALL+EXEC
NOTIFYFLAG FSD SYSLOG+WALL+EXEC

upssched.conf

bash
CMDSCRIPT /etc/nut/ups_notify_telegram
PIPEFN /run/nut/upssched.pipe
LOCKFN /run/nut/upssched.lock

AT ONBATT * START-TIMER onbatt 30
AT ONLINE * CANCEL-TIMER onbatt
AT ONLINE * EXECUTE online

AT LOWBATT * EXECUTE lowbat

AT FSD * EXECUTE fsd
AT SHUTDOWN * EXECUTE powerdown

AT COMMBAD * START-TIMER commbad 15
AT COMMOK * CANCEL-TIMER commbad
AT COMMOK * EXECUTE commok
AT NOCOMM * EXECUTE nocomm
AT NOPARENT * EXECUTE noparent

핵심은 START-TIMERCANCEL-TIMER 쌍이다.

ONBATT가 오면 바로 알리지 않고 30초짜리 타이머를 건다. 그 안에 ONLINE이 오면 타이머를 취소한다. 30초를 넘겨야 진짜 정전으로 보고 알림을 보낸다.

COMMBAD도 같은 방식으로 15초를 준다. USB가 잠깐 끊기는 건 흔하고 대부분 바로 돌아온다.

LOWBATT, FSD, SHUTDOWN은 타이머 없이 즉시 보낸다. 이 시점이면 이미 늦은 상황이라 지연시킬 이유가 없다.

알림 스크립트

CMDSCRIPT 에 지정한 파일은 EXECUTE 의 인자를 $1 로 받는다.

bash
#!/usr/bin/env bash
# NUT CMDSCRIPT

BOT_TOKEN=""
CHAT_ID=""
TIMEOUT=5

HOST="$(hostname)"
NOW="$(date '+%Y-%m-%d %H:%M:%S %Z')"
UPSNAME="${UPSNAME:-unknown}"
NOTIFYTYPE="${NOTIFYTYPE:-unknown}"
NOTIFYMSG="${NOTIFYMSG:-blank}"

TYPE="${1:-unknown}"

case "$TYPE" in
    online)   TITLE="Power Restored" ;;
    onbatt)   TITLE="On Battery" ;;
    lowbatt)  TITLE="Low Battery (Critical)" ;;
    fsd)      TITLE="Forced Shutdown (FSD)" ;;
    commok)   TITLE="UPS Communication Restored" ;;
    commbad)  TITLE="UPS Communication Lost" ;;
    powerdown) TITLE="Host Shutting Down" ;;
    nocomm)   TITLE="No UPS Available" ;;
    noparent) TITLE="upsmon Parent Lost" ;;
    *)        TITLE="UPS Event: ${TYPE}" ;;
esac

TEXT="$TITLE

Host: $HOST
UPS: $UPSNAME
Event: $NOTIFYTYPE
Time: $NOW
Info: $NOTIFYMSG"

curl -sS --max-time "$TIMEOUT" -X POST \
  "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
  --data-urlencode "chat_id=${CHAT_ID}" \
  --data-urlencode "text=${TEXT}" \
  --data-urlencode "disable_web_page_preview=true" \
  >/dev/null 2>&1

echo "$(date '+%F %T') Sent: $TYPE ($UPSNAME)" >> /etc/nut/nut-telegram.log

UPSNAME, NOTIFYTYPE, NOTIFYMSGupsmon 이 환경변수로 넘겨준다. 어느 UPS의 어떤 이벤트인지를 여기서 꺼내 쓴다.

--max-time 을 짧게 잡은 이유가 있다. 정전 중이면 인터넷도 같이 나갔을 수 있는데, 알림을 보내려다 스크립트가 오래 붙들려 있으면 종료 과정이 밀린다.

마지막 줄의 로그 파일은 알림이 실제로 나갔는지 나중에 확인하려고 남긴다. curl 출력은 버리고 있어서 이게 없으면 아무 흔적도 안 남는다.