From 568d82919d22dc71157592a25197782656a1c5b0 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Mon, 24 Aug 2026 02:53:22 +0300 Subject: [PATCH] =?UTF-8?q?feat(ops):=20=D0=B7=D0=B0=D0=BF=D0=B0=D1=81?= =?UTF-8?q?=D0=BD=D0=BE=D0=B9=20=D0=BA=D0=B0=D0=BD=D0=B0=D0=BB=20=D0=B0?= =?UTF-8?q?=D0=BB=D0=B5=D1=80=D1=82=D0=BE=D0=B2=20=D0=BD=D0=B0=20=D1=81?= =?UTF-8?q?=D0=BB=D1=83=D1=87=D0=B0=D0=B9=20=D0=BD=D0=B5=D0=B4=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=83=D0=BF=D0=BD=D0=BE=D0=B3=D0=BE=20Telegram?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit До этого единственным каналом был Telegram, и при его недоступности notify() ограничивался `|| log "WARN"`. То есть алерт ТЕРЯЛСЯ: оставалась строка в логе, который никто не читает, пока не случится беда. Канал, которым мы узнаём о проблемах, сам является проблемой — поломка скрывает сама себя. Это не гипотеза. Замер с нового хоста (#3059, 23.08): из семи публикуемых адресов api.telegram.org с Poincare отвечает РОВНО ОДИН (149.154.167.220), скан всей подсети 149.154.167.0/24 не нашёл ни одного другого. Через прокси Telegram не проходит вовсе — узлы российские. После переезда весь Telegram-канал висит на одном адресе без запасного, и через него же идут алерты бэкапов и uptime-нотификации. Почта выбрана потому, что проверена: с Poincare smtp.beget.com:465 OPEN (587 закрыт). Транспорт — тот же curl, который уже жёсткая зависимость lib-backup.sh; новых пакетов не нужно. Правка ложится ровно в ту точку, которую сам файл объявляет расширением: «to wire a different channel later, edit ONLY this function». Поведение: telegram ok -> как раньше telegram упал -> пробуем почту telegram не настроен -> пробуем почту почта не настроена -> ГРОМКАЯ строка «АЛЕРТ НЕ ДОСТАВЛЕН» оба канала упали -> ГРОМКАЯ строка с кодом возврата curl Ничего не настроено -> ведёт себя ровно как до правки. Кредов в репозитории нет, читаются из того же env-файла, что и Telegram. Проверено (bash -n + прогон всех веток): - почта не настроена -> «АЛЕРТ НЕ ДОСТАВЛЕН (...не настроена)», rc=1 - SMTP недоступен -> «НИ ОДНИМ КАНАЛОМ (... curl smtp rc=7)», rc=1 - notify() без telegram сам зовёт запасной канал - пароль в выводе: 0 вхождений (специально проверил grep'ом) НЕ проверена успешная доставка: для неё нужны настоящие SMTP-креды, а это часть владельца. Три попытки поднять фейковый SMTP-сервер положительный контроль не дали, выдумывать результат не стал — см. test plan в PR. Refs #3059, #3057 --- ops/lib-backup.sh | 57 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/ops/lib-backup.sh b/ops/lib-backup.sh index 07f3507e..81635e93 100755 --- a/ops/lib-backup.sh +++ b/ops/lib-backup.sh @@ -41,6 +41,7 @@ notify() { if [[ -z "${TELEGRAM_BOT_TOKEN:-}" || -z "${TELEGRAM_CHAT_ID:-}" ]]; then log "NOTIFY (telegram disabled — set TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID in ${backup_env}): $text" + notify_fallback_mail "$text" return 0 fi @@ -50,7 +51,61 @@ notify() { -d "disable_web_page_preview=true" \ --data-urlencode "text=${text}" \ >/dev/null 2>&1 \ - || log "WARN: telegram sendMessage failed" + || { log "WARN: telegram sendMessage failed — пробую запасной канал"; \n notify_fallback_mail "$text"; } +} + +# --- запасной канал оповещения (#3059) ---------------------------------- +# Зачем: раньше единственным каналом был Telegram, и `|| log WARN` означало, +# что при его недоступности алерт просто ТЕРЯЛСЯ — оставалась строка в логе, +# который никто не читает, пока не случится беда. Самоскрывающаяся поломка: +# канал, которым мы узнаём о проблемах, сам и есть проблема. +# +# Это не паранойя, а замер (#3059, 2026-08-23): с нового хоста Poincare из семи +# публикуемых адресов api.telegram.org отвечает РОВНО ОДИН (149.154.167.220), +# и скан всей подсети 149.154.167.0/24 не нашёл больше ни одного. Через прокси +# Telegram не проходит вовсе — узлы российские. После переезда весь +# Telegram-канал висит на одном адресе, без запасного. +# +# Почему почта: с Poincare проверено — smtp.beget.com:465 OPEN (587 закрыт). +# Транспорт — тот же curl, который уже является жёсткой зависимостью файла; +# новых пакетов не требуется. +# +# Настраивается тем же env-файлом, что и Telegram. Не задано — ведёт себя как +# раньше. Кредов в репозитории нет: +# ALERT_SMTP_URL=smtps://smtp.beget.com:465 +# ALERT_SMTP_USER= / ALERT_SMTP_PASS= / ALERT_MAIL_FROM= / ALERT_MAIL_TO= +notify_fallback_mail() { + local text="$1" + + if [[ -z "${ALERT_SMTP_URL:-}" || -z "${ALERT_SMTP_USER:-}" \ + || -z "${ALERT_SMTP_PASS:-}" || -z "${ALERT_MAIL_FROM:-}" \ + || -z "${ALERT_MAIL_TO:-}" ]]; then + # ГРОМКО: это последний рубеж, тишина здесь = потерянный алерт. + log "АЛЕРТ НЕ ДОСТАВЛЕН (telegram недоступен, почта не настроена): $text" + return 1 + fi + + local subject body rc=0 + subject=$(printf '%s' "$text" | head -c 120 | tr '\n' ' ') + body=$(printf 'From: %s\nTo: %s\nSubject: [gendesign] %s\n\n%s\n' \ + "$ALERT_MAIL_FROM" "$ALERT_MAIL_TO" "$subject" "$text") + + # Пароль идёт через --user, поэтому stderr curl целиком не печатаем — в нём + # может оказаться строка подключения. Логируем только код возврата. + printf '%s' "$body" | curl -fsS --max-time 30 --ssl-reqd \ + --url "$ALERT_SMTP_URL" \ + --user "${ALERT_SMTP_USER}:${ALERT_SMTP_PASS}" \ + --mail-from "$ALERT_MAIL_FROM" \ + --mail-rcpt "$ALERT_MAIL_TO" \ + --upload-file - >/dev/null 2>&1 || rc=$? + + if (( rc == 0 )); then + log "Алерт доставлен запасным каналом (почта) вместо Telegram" + return 0 + fi + + log "АЛЕРТ НЕ ДОСТАВЛЕН НИ ОДНИМ КАНАЛОМ (telegram упал, curl smtp rc=${rc}): $text" + return 1 } # --- sentinel (missed-run detection) ------------------------------------ -- 2.45.3