Перейти к содержимому

Уведомление о пропаже питания (ИБП / UPS)

Когда пропадает электричество, у вас есть ровно столько времени, сколько держит ИБП — обычно несколько минут. Этого хватает, чтобы корректно погасить сервер вручную или хотя бы понять, что скоро всё выключится. Push с текущим зарядом батареи и оставшимся временем работы позволяет отреагировать, пока ещё есть запас. Часто внезапное отключение питания коррелирует с неожиданными перезагрузками — см. неожиданная перезагрузка.

Скрипт каждую минуту опрашивает ИБП через NUT (upsc) и смотрит поле ups.status:

  • OL — питание от сети (всё в норме);
  • OB / ONBATT — сервер работает от батареи (пропало питание);
  • LB — низкий заряд, скоро выключение.

Логика уведомлений:

  • переход на батарею — сообщение с высоким приоритетом (9) 🔌;
  • низкий заряд батареи — приоритет 10 🔋, самый громкий сигнал;
  • возврат к питанию от сети — спокойное сообщение восстановления, приоритет 4 ✅.

В сообщении показываем заряд батареи (battery.charge) и расчётное время работы (battery.runtime). Чтобы не получать одно и то же каждую минуту, храним состояние (ok / onbatt / lowbatt) в /var/lib/notifly-ups/.

Предполагается, что NUT уже настроен и upsc <имя_ИБП>@localhost отдаёт данные. Имя ИБП можно узнать командой upsc -l. Сохраните как /usr/local/bin/notifly-ups-check:

#!/usr/bin/env bash
set -eu
set -a; source /etc/notifly.env; set +a
UPS="${1:-ups@localhost}" # имя ИБП, см. `upsc -l`
STATE_DIR=/var/lib/notifly-ups
mkdir -p "$STATE_DIR"
STATE_FILE="$STATE_DIR/state"
HOST=$(hostname -s)
# Читаем текущие показатели ИБП
STATUS=$(upsc "$UPS" ups.status 2>/dev/null || echo "UNKNOWN")
CHARGE=$(upsc "$UPS" battery.charge 2>/dev/null || echo "?")
RUNTIME=$(upsc "$UPS" battery.runtime 2>/dev/null || echo "")
# battery.runtime приходит в секундах — переводим в минуты для человека
if [ -n "$RUNTIME" ]; then
MINS=$(( RUNTIME / 60 ))
RUNLEFT="${MINS} мин"
else
RUNLEFT="н/д"
fi
# Определяем уровень по флагам в ups.status
LEVEL=ok
case " $STATUS " in
*" OB "*|*" ONBATT "*) LEVEL=onbatt ;;
esac
case " $STATUS " in
*" LB "*) LEVEL=lowbatt ;; # низкий заряд имеет приоритет над onbatt
esac
LAST=$(cat "$STATE_FILE" 2>/dev/null || echo ok)
if [ "$LEVEL" != "$LAST" ]; then
case "$LEVEL" in
onbatt)
/usr/local/bin/notifly-send \
"🔌 Пропало питание — $HOST" \
"Сервер перешёл на ИБП. Заряд $CHARGE%, хватит на $RUNLEFT." 9
;;
lowbatt)
/usr/local/bin/notifly-send \
"🔋 Низкий заряд ИБП — $HOST" \
"Осталось $CHARGE% ($RUNLEFT). Скоро сервер выключится!" 10
;;
ok)
/usr/local/bin/notifly-send \
"✅ Питание восстановлено — $HOST" \
"Сервер снова от сети. Заряд батареи $CHARGE%." 4
;;
esac
echo "$LEVEL" > "$STATE_FILE"
fi

Сделайте исполняемым:

Окно терминала
sudo chmod +x /usr/local/bin/notifly-ups-check

Если вы используете apcupsd вместо NUT, данные читаются командой apcaccess status. Поле STATUS содержит ONLINE или ONBATT, BCHARGE — заряд в процентах, TIMELEFT — оставшееся время. Вариант скрипта:

#!/usr/bin/env bash
set -eu
set -a; source /etc/notifly.env; set +a
STATE_DIR=/var/lib/notifly-ups
mkdir -p "$STATE_DIR"
STATE_FILE="$STATE_DIR/state"
HOST=$(hostname -s)
# apcaccess отдаёт "КЛЮЧ : значение" — вытаскиваем нужные поля
STAT=$(apcaccess status 2>/dev/null || echo "")
STATUS=$(echo "$STAT" | awk -F: '/^STATUS/ {gsub(/ /,"",$2); print $2}')
CHARGE=$(echo "$STAT" | awk -F: '/^BCHARGE/ {gsub(/ /,"",$2); print $2+0}')
RUNLEFT=$(echo "$STAT" | awk -F: '/^TIMELEFT/ {print $2}' | xargs)
LEVEL=ok
case "$STATUS" in
*ONBATT*) LEVEL=onbatt ;;
esac
# apcupsd отдаёт флаг низкого заряда через "STATUS: ONBATT LOWBATT"
case "$STATUS" in
*LOWBATT*) LEVEL=lowbatt ;;
esac
LAST=$(cat "$STATE_FILE" 2>/dev/null || echo ok)
if [ "$LEVEL" != "$LAST" ]; then
case "$LEVEL" in
onbatt)
/usr/local/bin/notifly-send \
"🔌 Пропало питание — $HOST" \
"Сервер перешёл на ИБП. Заряд $CHARGE%, хватит на $RUNLEFT." 9
;;
lowbatt)
/usr/local/bin/notifly-send \
"🔋 Низкий заряд ИБП — $HOST" \
"Осталось $CHARGE% ($RUNLEFT). Скоро сервер выключится!" 10
;;
ok)
/usr/local/bin/notifly-send \
"✅ Питание восстановлено — $HOST" \
"Сервер снова от сети. Заряд батареи $CHARGE%." 4
;;
esac
echo "$LEVEL" > "$STATE_FILE"
fi

Питание нужно проверять чаще, чем диск, — раз в минуту, чтобы не пропустить короткий разряд ИБП.

Окно терминала
sudo crontab -e

Добавьте строку (передайте имя вашего ИБП, если оно не ups):

* * * * * /usr/local/bin/notifly-ups-check ups@localhost >/dev/null 2>&1

/etc/systemd/system/notifly-ups.service:

[Unit]
Description=Notifly UPS power check
[Service]
Type=oneshot
ExecStart=/usr/local/bin/notifly-ups-check ups@localhost

/etc/systemd/system/notifly-ups.timer:

[Unit]
Description=Run notifly-ups every minute
[Timer]
OnBootSec=1min
OnUnitActiveSec=1min
[Install]
WantedBy=timers.target

Активируем:

Окно терминала
sudo systemctl daemon-reload
sudo systemctl enable --now notifly-ups.timer
sudo systemctl list-timers notifly-ups.timer

Многие бытовые ИБП под Windows не имеют своего демона, но отдают заряд батареи операционной системе. Читаем Win32_Battery через CIM: поле BatteryStatus (1 = работа от батареи / разряд, 2 = питание от сети), EstimatedChargeRemaining — заряд в процентах, EstimatedRunTime — оставшееся время в минутах. Используем общую функцию Send-Notifly из шаблона sysadmin/index.

C:\scripts\Notifly-UPS-Check.ps1
. C:\scripts\Notifly.ps1
$StateDir = "C:\ProgramData\Notifly\ups-state"
New-Item -ItemType Directory -Path $StateDir -Force | Out-Null
$StateFile = Join-Path $StateDir "state.txt"
$Host = $env:COMPUTERNAME
$bat = Get-CimInstance Win32_Battery | Select-Object -First 1
if (-not $bat) { return } # нет батареи/ИБП — выходим
$charge = $bat.EstimatedChargeRemaining
$runtime = $bat.EstimatedRunTime # минуты; 71582788 = "не рассчитано"
$runLeft = if ($runtime -and $runtime -lt 1000000) { "$runtime мин" } else { "н/д" }
# BatteryStatus: 1 = разряд (на батарее), 2 = питание от сети (AC)
$level = "ok"
if ($bat.BatteryStatus -eq 1) { $level = "onbatt" }
if ($charge -le 20 -and $bat.BatteryStatus -eq 1) { $level = "lowbatt" }
$last = if (Test-Path $StateFile) { Get-Content $StateFile -Raw } else { "ok" }
if ($level -ne $last.Trim()) {
switch ($level) {
"onbatt" { Send-Notifly -Title "🔌 Пропало питание — $Host" `
-Message "Сервер перешёл на ИБП. Заряд $charge%, хватит на $runLeft." -Priority 9 }
"lowbatt" { Send-Notifly -Title "🔋 Низкий заряд ИБП — $Host" `
-Message "Осталось $charge% ($runLeft). Скоро сервер выключится!" -Priority 10 }
"ok" { Send-Notifly -Title "✅ Питание восстановлено — $Host" `
-Message "Сервер снова от сети. Заряд батареи $charge%." -Priority 4 }
}
$level | Set-Content $StateFile
}

Регистрация в Task Scheduler (от администратора, каждую минуту от SYSTEM):

Окно терминала
$Action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File C:\scripts\Notifly-UPS-Check.ps1"
$Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Minutes 1)
$Princ = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "Notifly UPS Check" `
-Action $Action -Trigger $Trigger -Principal $Princ -Description "Notifly: контроль питания ИБП"
  • Успеваете погасить сервер вручную — push приходит в момент перехода на батарею, пока есть несколько минут запаса, а не после жёсткого выключения.
  • Видите остаток батареи прямо в уведомлении — заряд и расчётное время работы сразу подсказывают, сколько у вас есть.
  • Три уровня без спама (okonbattlowbatt): пока состояние не меняется, уведомлений нет; отдельный громкий сигнал (приоритет 10) на низком заряде не даст проспать выключение.
  • Добавить приоритет 10 с повторяющимся звуком именно для события низкого заряда — чтобы разбудить дежурного.
  • Приложить полный вывод upsc ups@localhost (напряжение, нагрузка, температура) через поле extras.
  • Связать с детектором неожиданной перезагрузки: если после пропажи питания сервер всё же перезагрузился, вы получите обе истории.