🔌⚡ ИБП + Proxmox: корректное автовыключение через NUT (10 минут ожидания)

В этой заметке я фиксирую рабочую конфигурацию для Proxmox (Debian) и ИБП ExeGate NEO Smart (USB), чтобы при пропадании сети сервер ждал 10 минут, а затем корректно выключался (вместо «аварийного обрыва питания»).

Цель: переживать краткие перебои и запуск генератора, но не разряжать батарею в ноль и не ловить «грязные» выключения. 💾✅


🧠 Логика работы (простая схема)

  1. ⚡ Пропало питание (ИБП перешёл в OB — On Battery).
  2. ⏳ Запускается таймер 600 секунд (10 минут).
  3. 🔁 Если питание вернулось (ONLINE) — таймер отменяется.
  4. 🛑 Если через 13 минут ИБП всё ещё на батарее (OB) — сервер выполняет корректный poweroff.
🧩 Почему не «по проценту заряда»? Потому что у бюджетных ИБП оценки времени/заряда могут быть неточными, а таймер + проверка OB — предсказуемо и воспроизводимо.

📦 Что должно быть установлено

  • 🧰 NUT (Network UPS Tools): nut-server и nut-monitor
  • 🧪 Для проверки: upsc, journalctl
  • 🔐 sudo (для права выключать хост из notify-скрипта)
apt update
apt install -y nut sudo

🔎 Проверка, что ИБП корректно определяется по USB

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

1️⃣ Проверка через lsusb

Подключаем ИБП по USB и смотрим список USB-устройств:

lsusb

Пример вывода в рабочей конфигурации:

Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0557:7000 ATEN International Co., Ltd Hub
Bus 001 Device 003: ID 0665:5161 Cypress Semiconductor USB to Serial
Bus 001 Device 004: ID 0557:2419 ATEN International Co., Ltd Virtual mouse/keyboard device
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub

В данном случае ИБП определяется как устройство 0665:5161 (USB to Serial). Это нормально для многих line-interactive ИБП, использующих megatec-совместимый протокол.

⚠️ Если ИБП не появляется в lsusb — дальше идти бессмысленно: проверяем кабель, порт, питание ИБП и сам USB-контроллер.

2️⃣ Сканирование ИБП утилитой nut-scanner

Теперь проверяем, может ли NUT подобрать подходящий драйвер. Используем только USB-сканирование:

nut-scanner -U   # Только USB-сканирование

Пример вывода:

Cannot load IPMI library (libfreeipmi.so.17) : file not found. IPMI search disabled.
Scanning USB bus.
[nutdev1]
        driver = "nutdrv_qx"
        port = "auto"
        vendorid = "0665"
        productid = "5161"
        product = "USB to Serial"
        vendor = "WCHCH544"
        bus = "001"
        device = "003"
        busport = "011"
        ###NOTMATCHED-YET###bcdDevice = "0002"

Ключевые моменты:

  • ✅ найдено USB-устройство с тем же vendorid/productid, что и в lsusb
  • ✅ NUT предлагает драйвер nutdrv_qx
  • ⚠️ пометка NOTMATCHED-YET — это нормально для недорогих ИБП и не мешает работе
🧠 На практике, если nut-scanner уверенно находит устройство и предлагает драйвер — этого достаточно для корректной работы NUT.

3️⃣ Сканирование ИБП командой upsc ups@localhost

upsc ups@localhost

В норме вы увидите что-то вроде:

  • ups.status: OL — питание от сети (On Line)
  • ups.status: OB — питание от батареи (On Battery)
  • 📊 ups.load: ... — нагрузка по мнению ИБП

🗂️ Итоговая конфигурация файлов NUT

1) /etc/nut/nut.conf 🧩

Режим standalone: монитор и сервер на одной машине.

MODE=standalone

2) /etc/nut/ups.conf 🔌

Пример (драйвер определяется по факту работы у вас; ниже — как было в рабочей конфигурации):


[ups]
driver = nutdrv_qx
port = auto
vendorid = 0665
productid = 5161
protocol = megatec
desc = "ExeGate NEO Smart LHB-1000 USB (Megatec)"

3) /etc/nut/upsd.users 🔐

⚠️ Пароль лучше без символа #, потому что он может ломать разбор строк в конфиге.

[madmentat]
  password = XXX
  upsmon master

Права на файл (чтобы убрать warning про world readable):

chmod 640 /etc/nut/upsd.users
chown root:nut /etc/nut/upsd.users

4) /etc/nut/upsmon.conf 👀

Минимальный понятный конфиг, плюс уведомления для таймера:

MONITOR ups@localhost 1 madmentat XXX master
POWERDOWNFLAG /etc/killpower
SHUTDOWNCMD "/sbin/shutdown -h now"

NOTIFYCMD /usr/local/sbin/nut-ob-timer.sh
NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG ONLINE SYSLOG+EXEC

🧯 Права на выключение (sudoers) 🔐

На практике notify-скрипт запускается не всегда от root, поэтому выключение делаем через sudo без пароля для пользователя nut.

Файл:

/etc/sudoers.d/nut-shutdown

Содержимое (одна строка):

nut ALL=(root) NOPASSWD: /sbin/shutdown

Проверка синтаксиса:

visudo -cf /etc/sudoers.d/nut-shutdown

🧾 Финальная версия скрипта таймера (10 минут) ⏳

Скрипт реагирует на события ONBATT/ONLINE от upsmon. Он ставит таймер на 600 секунд (10 минут) и отменяет его, если питание вернулось.

Файл:

/usr/local/sbin/nut-ob-timer.sh
#!/bin/bash
# nut-ob-timer.sh — скрипт для graceful shutdown через заданное время на батарее
# Обновлённая версия с поддержкой отключения самого ИБП (touch /etc/killpower)
# Автор оригинала: madmentat.ru
# Доработки: добавление POWERDOWNFLAG для отключения UPS после shutdown
TIMER=600 # Время в секундах, после которого выключаем сервер (подстрой под свою батарею)
LOG_TAG="nut-ob-timer" # Тег для journalctl -t nut-ob-timer
UPS_NAME="ups" # Имя UPS из /etc/nut/ups.conf (у тебя [ups])
log() {
echo "$@" | logger -t "${LOG_TAG}"
}
# Проверка, что мы запущены из NOTIFYCMD в upsmon.conf
if [[ -z "$NOTIFYTYPE" ]]; then
log "ERROR: Script not called from upsmon (NOTIFYTYPE missing)"
exit 1
fi
log "Started: NOTIFYTYPE=$NOTIFYTYPE"
# Основная логика — только при переходе на батарею (ONBATT)
if [[ "$NOTIFYTYPE" == "ONBATT" ]]; then
log "UPS switched to battery. Starting ${TIMER}s timer."
sleep "$TIMER"
# Проверяем, всё ли ещё на батарее
if upsc "${UPS_NAME}@localhost" 2>/dev/null | grep -q '^ups.status: .*OB'; then
log "${TIMER}s elapsed and UPS still on battery (OB) — initiating graceful shutdown"
# КЛЮЧЕВАЯ СТРОКА: создаём флаг, чтобы upsmon отключил ИБП после shutdown
touch /etc/killpower
log "Created POWERDOWNFLAG /etc/killpower — UPS will power off outlets after system halt"
# Пробуем systemctl (systemd)
if command -v systemctl >/dev/null 2>&1; then
log "Executing: systemctl poweroff"
systemctl poweroff 2>&1 | while read -r line; do log "systemctl: $line"; done
rc=${PIPESTATUS[0]}
log "systemctl poweroff exit code: $rc"
else
rc=127
log "systemctl not found"
fi
# Если systemctl не сработал — fallback на sudo shutdown
if [[ "${rc:-1}" -ne 0 ]]; then
if command -v sudo >/dev/null 2>&1; then
log "Fallback: executing sudo /sbin/shutdown -h now"
sudo -n /sbin/shutdown -h now "CRITICAL: UPS on battery for ${TIMER}s" \
2>&1 | while read -r line; do log "sudo shutdown: $line"; done
else
log "ERROR: neither systemctl nor sudo available — cannot shutdown"
fi
fi
else
log "${TIMER}s elapsed but UPS no longer on battery (OB flag cleared) — cancelling shutdown"
fi
elif [[ "$NOTIFYTYPE" == "ONLINE" ]]; then
# Если сеть вернулась до истечения таймера — просто логируем
log "UPS returned to line power (ONLINE) — timer cancelled if running"
else
log "Ignored NOTIFYTYPE=$NOTIFYTYPE"
fi
exit 0

Права и применение:

chmod +x /usr/local/sbin/nut-ob-timer.sh
systemctl restart nut-server
systemctl restart nut-monitor

🧪 Проверка работы (быстрый тест) ✅

Чтобы не ждать 13 минут, можно временно поставить TIMER=30, выполнить тест, затем вернуть TIMER=780.

  1. 👀 Открыть лог в реальном времени:
    journalctl -f -t nut-ob-timer
  2. 🔌 Вытащить вилку ИБП из сети 220В (сервер остаётся подключён к ИБП).
  3. ✅ Убедиться, что появляется ONBATT и таймер стартует.
  4. 🔁 Вставить вилку обратно — увидеть ONLINE и отмену таймера.

Также полезно:

upsc ups@localhost | egrep 'ups.status|ups.load|battery.voltage|input.voltage'

🧷 Важный нюанс: автозапуск после выключения 🔁

ИБП обычно не “нажимает кнопку питания” за вас. Чтобы сервер включался сам после возвращения сети, настройте BIOS/UEFI:

  • ⚙️ Restore on AC Power Loss / AC Back / Power On after Power FailurePower On или Last State

🧾 Команды для быстрой диагностики 🧰

  • Статус служб:
    systemctl status nut-server
    systemctl status nut-monitor
  • Журнал уведомлений таймера:
    journalctl -t nut-ob-timer -n 200 --no-pager
  • Проверка прошлого выключения/перезагрузки:
    journalctl -b -1 -n 300 --no-pager
    last -x | head -n 20

✅ Итог

Теперь сервер: ⚡ переживает короткие отключения, ⏳ ждёт 10 минут, 🛑 выключается корректно, 🔁 и может сам включиться при правильной настройке BIOS.

✍️ Примечание: если хочется уменьшить «долгое выключение», обычно это связано с таймаутами остановки ВМ/контейнеров или конкретным гостем, который не реагирует на shutdown. Это отдельная тема для следующей заметки. 🐢➡️🐇

 

P.S.

По поводу включения обратно - абосрамс💩... Сам ИБП этого не умеет. Стало быть, надо городить какую-то хитровыебанную систему с Wi-Fi розетками и микроконтроллерами... В принципе, хуле там даже думать. Микроконтроллер с Вайфаем есть, БП для него есть, надо только прикупить Wi-Fi реле, чтобы вырубать питание сервере насовсем, после чего он нормально стартанет. И еще надо будет прописать юнит systemd, который запустит уже имеющийся repair-скрипт для поднятия всех контейнеров и ВМ на хосте Proxmox. Говно-вопроса, короче...

...