Уведомления о неудачных cron-задачах
По умолчанию cron шлёт ошибки в локальную почту root, которую никто не читает. Заменим этот канал на Notifly: каждая упавшая задача → push на телефон с последними строками вывода.
Способ 1: универсальная обёртка
Заголовок раздела «Способ 1: универсальная обёртка»Используйте уже готовый notifly-wrap
из рецепта про бэкапы. В cron задачи будут выглядеть так:
*/10 * * * * www-data /usr/local/bin/notifly-wrap "Sync ratings" -- /usr/local/bin/sync-ratings.sh0 * * * * root /usr/local/bin/notifly-wrap "Yum updates" -- yum -y update30 3 * * * root /usr/local/bin/notifly-wrap "Logrotate" -- logrotate -f /etc/logrotate.confЗелёные «✅» можно подавить, изменив обёртку — отправлять только при rc != 0.
Минимальная «только-падения» версия:
#!/usr/bin/env bashset -euset -a; source /etc/notifly.env; set +aNAME="${1:?}"; shift; [ "${1:-}" = "--" ] && shiftLOG=$(mktemp)if ! "$@" >"$LOG" 2>&1; then RC=$? /usr/local/bin/notifly-send \ "❌ cron: $NAME (rc=$RC) — $(hostname -s)" \ "$(tail -c 1500 "$LOG")" 8 rm -f "$LOG"; exit "$RC"firm -f "$LOG"Способ 2: глобальный MAILTO через хук
Заголовок раздела «Способ 2: глобальный MAILTO через хук»Если не хочется править все cron-задачи, можно перехватить почту, которую cron шлёт root, и отправить её в Notifly.
В /etc/aliases (или /etc/postfix/aliases) добавьте:
root: |/usr/local/bin/notifly-mailpipe/usr/local/bin/notifly-mailpipe:
#!/usr/bin/env bashset -euset -a; source /etc/notifly.env; set +a
BODY=$(cat | head -c 2000)[ -z "$BODY" ] && exit 0
/usr/local/bin/notifly-send \ "📬 cron-вывод на $(hostname -s)" \ "$BODY" 6sudo chmod +x /usr/local/bin/notifly-mailpipesudo newaliasesТеперь любой cron-job, который что-то выводит в stdout/stderr, превращается в push-уведомление. Это «грязный, но универсальный» способ — годится, чтобы быстро накрыть унаследованный сервер.
Способ 3: проверка «канарейки»
Заголовок раздела «Способ 3: проверка «канарейки»»Иногда сам cron-демон может не запуститься (например, после редактирования crontab с ошибкой). Заведите простой «канарейку», которая шлёт сообщение, если не было heartbeat:
*/5 * * * * root touch /tmp/cron-aliveИ отдельный systemd-таймер:
[Unit]Description=Cron canary check
[Timer]OnCalendar=*:0/30Persistent=true
[Install]WantedBy=timers.target[Service]Type=oneshotEnvironmentFile=/etc/notifly.envExecStart=/bin/bash -c '\ AGE=$(( $(date +%%s) - $(stat -c %%Y /tmp/cron-alive 2>/dev/null || echo 0) )); \ if [ "$AGE" -gt 900 ]; then \ /usr/local/bin/notifly-send "⚠️ Cron мёртв на $(hostname -s)" \ "Heartbeat-файл старше 15 минут (возраст ${AGE}s)." 10; \ fi'sudo systemctl daemon-reloadsudo systemctl enable --now notifly-cron-canary.timerWindows: Task Scheduler + обёртка
Заголовок раздела «Windows: Task Scheduler + обёртка»Windows-аналог cron — Task Scheduler. Он самостоятельно отслеживает exit-code,
поэтому вместо «обёрток» проще всего подписаться на события «задача завершилась
с ошибкой» в журнале Microsoft-Windows-TaskScheduler/Operational (Event ID 203).
Способ 1: глобальный ватчер всех задач
Заголовок раздела «Способ 1: глобальный ватчер всех задач»param([string]$TaskName, [string]$ResultCode). C:\scripts\Notifly.ps1
Send-Notifly ` -Title "❌ Task Scheduler: $TaskName — $env:COMPUTERNAME" ` -Message "Код возврата: $ResultCode" ` -Priority 8Подписка на все фейлы Task Scheduler:
$xml = @"<QueryList> <Query Id="0" Path="Microsoft-Windows-TaskScheduler/Operational"> <Select Path="Microsoft-Windows-TaskScheduler/Operational"> *[System[(EventID=203 or EventID=323)]] </Select> </Query></QueryList>"@$Trigger = New-ScheduledTaskTrigger -AtStartup$Trigger.Subscription = $xml$Action = New-ScheduledTaskAction -Execute "powershell.exe" ` -Argument "-NoProfile -ExecutionPolicy Bypass -File C:\scripts\Notifly-TaskFailed.ps1 -TaskName 'Unknown' -ResultCode 'see Event Log'"Register-ScheduledTask -TaskName "Notifly Watch Failed Tasks" -Trigger $Trigger -Action $Action ` -Principal (New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest)Способ 2: персональная обёртка для одной задачи
Заголовок раздела «Способ 2: персональная обёртка для одной задачи»Аналог notifly-wrap — запускает команду, ловит exit-code, при ошибке шлёт хвост лога:
param([Parameter(Mandatory)] [string]$Name, [Parameter(Mandatory, ValueFromRemainingArguments)] [string[]]$Cmd). C:\scripts\Notifly.ps1
$log = New-TemporaryFile$start = Get-Date& $Cmd[0] $Cmd[1..($Cmd.Count-1)] *> $log$rc = $LASTEXITCODE$dur = ((Get-Date) - $start).TotalSeconds
if ($rc -ne 0) { $tail = Get-Content $log -Tail 30 | Out-String Send-Notifly -Title "❌ $Name (rc=$rc) — $env:COMPUTERNAME" ` -Message "Длительность: $([int]$dur)s`n`n$tail" ` -Priority 8}Remove-Item $log -Forceexit $rcИспользование в задаче: в поле «Action» укажите
powershell.exe -NoProfile -File C:\scripts\Notifly-Wrap.ps1 -Name "Ночной бэкап" robocopy ....
- Никаких «потерянных» ошибок в локальной почте. Каждая упавшая задача = push-уведомление.
- Контекст в кармане — последние 1500 символов stderr-а, обычно этого достаточно для диагностики.
- Канарейка ловит худший случай — когда сам cron не работает.
Что улучшить дальше
Заголовок раздела «Что улучшить дальше»- Отдельные каналы Notifly для разных типов задач (ETL, бэкап, мониторинг) — чтобы можно было фильтровать в админке и в клиенте.
- Хранить «время последнего успеха» в Prometheus textfile collector и алертить, если он не обновляется N часов.