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

Уведомление о перезагрузке сервера

Сервер перезагрузился ночью — а вы узнали об этом только утром, когда пользователи пожаловались на «отвалившиеся» сессии. Внезапный ребут почти всегда означает что-то серьёзное: паника ядра, watchdog, отключение питания или чьё-то reboot по SSH. Уведомление в момент загрузки экономит часы расследования.

Создадим systemd-юнит типа oneshot, который запускается один раз при каждой загрузке (WantedBy=multi-user.target). Скрипт:

  • собирает uptime, время последней загрузки и причину;
  • отличает плановую перезагрузку от неожиданной через маркер-файл: перед штатным reboot/shutdown мы кладём маркер, при загрузке — проверяем его;
  • при неожиданном ребуте шлёт сообщение с высоким приоритетом, при плановом — обычное «сервер вернулся».

Сохраните как /usr/local/bin/notifly-reboot-check:

#!/usr/bin/env bash
set -eu
set -a; source /etc/notifly.env; set +a
HOST=$(hostname -s)
MARKER=/var/lib/notifly-reboot/planned
# Время последней загрузки и аптайм в человекочитаемом виде
BOOT_TIME=$(uptime -s)
UPTIME_H=$(uptime -p)
# Последние строки о причинах прошлого выключения — часто видно panic/power loss
LAST_REBOOT=$(last -x -n 5 reboot shutdown 2>/dev/null | head -5 || true)
if [ -f "$MARKER" ]; then
# Маркер на месте — значит выключение было штатным
rm -f "$MARKER"
/usr/local/bin/notifly-send \
"🔄 $HOST перезагрузился (планово)" \
"Загрузка: $BOOT_TIME
Аптайм: $UPTIME_H" 4
else
# Маркера нет — перезагрузка была неожиданной
DMESG_ERR=$(journalctl -k -b -1 -p err --no-pager 2>/dev/null | tail -5 || true)
/usr/local/bin/notifly-send \
"⚠️ $HOST перезагрузился НЕОЖИДАННО" \
"Загрузка: $BOOT_TIME
Аптайм: $UPTIME_H
Последние ребуты:
$LAST_REBOOT
Ошибки прошлого сеанса:
$DMESG_ERR" 9
fi

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

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

Перед штатным выключением нужно положить маркер. Проще всего сделать это отдельным юнитом, который срабатывает на остановку системы:

/etc/systemd/system/notifly-reboot-marker.service:

[Unit]
Description=Mark planned reboot for Notifly
DefaultDependencies=no
Before=shutdown.target reboot.target halt.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/mkdir -p /var/lib/notifly-reboot
ExecStop=/bin/touch /var/lib/notifly-reboot/planned
[Install]
WantedBy=multi-user.target

Юнит держится «поднятым» всё время работы системы, а маркер создаёт ExecStop — то есть ровно в момент штатного systemctl reboot/shutdown. При внезапном падении питания ExecStop не отработает, маркера не будет — и мы поймём, что ребут был неожиданным.

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

[Unit]
Description=Notifly reboot notification
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/notifly-reboot-check
[Install]
WantedBy=multi-user.target

Активируем оба юнита:

Окно терминала
sudo systemctl daemon-reload
sudo systemctl enable --now notifly-reboot-marker.service
sudo systemctl enable notifly-reboot.service

Проверить вручную:

Окно терминала
sudo systemctl start notifly-reboot.service

На Windows момент загрузки виден в System Event Log: Event ID 6005 («The Event log service was started» — штатный старт), 6008 («The previous system shutdown was unexpected» — неожиданный) и 6009 (версия ОС при загрузке). Подпишемся на эти события. Используется общая функция Send-Notifly из шаблона sysadmin/index.

C:\scripts\Notifly-Reboot-Check.ps1
. C:\scripts\Notifly.ps1
$Host = $env:COMPUTERNAME
$uptime = (Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
$boot = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
# Было ли предыдущее выключение неожиданным? (Event 6008 в System)
$unexpected = Get-WinEvent -FilterHashtable @{LogName='System'; Id=6008} -MaxEvents 1 -ErrorAction SilentlyContinue
$isUnexpected = $unexpected -and $unexpected.TimeCreated -gt (Get-Date).AddMinutes(-15)
if ($isUnexpected) {
Send-Notifly -Title "⚠️ $Host перезагрузился НЕОЖИДАННО" `
-Message "Загрузка: $boot`nАптайм: $([int]$uptime.TotalMinutes) мин.`nПредыдущее выключение было аварийным (Event 6008)." `
-Priority 9
} else {
Send-Notifly -Title "🔄 $Host перезагрузился (планово)" `
-Message "Загрузка: $boot`nАптайм: $([int]$uptime.TotalMinutes) мин." `
-Priority 4
}

Регистрация задачи на старт системы:

Окно терминала
$Trigger = New-ScheduledTaskTrigger -AtStartup
$Trigger.Delay = "PT1M" # подождать минуту после загрузки
$Action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File C:\scripts\Notifly-Reboot-Check.ps1"
$Princ = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "Notifly Reboot Check" `
-Action $Action -Trigger $Trigger -Principal $Princ -Description "Notifly: уведомление о перезагрузке"
  • Ноль внезапных ребутов «в тишине». Любая перезагрузка сразу видна на телефоне.
  • Планово или нет — понятно сразу. Маркер-файл отделяет ваши собственные reboot от аварийных, так что вы не привыкаете игнорировать зелёные сообщения.
  • Контекст в кармане. В сообщении сразу время загрузки, аптайм и ошибки прошлого сеанса — часто этого достаточно, чтобы понять причину.
  • Прикладывать полный вывод journalctl -k -b -1 -p warning через поле extras, чтобы разбирать panic прямо в приложении.
  • Связать с heartbeat-проверкой: если после ребута сервер так и не прислал сигнал жизни — значит, он не поднялся.
  • Считать частоту ребутов: три неожиданных перезагрузки за час — это уже железная проблема, стоит поднять приоритет до 10.