Ребренд Мониторинг Орбита -> Железный Егорин; accent green->amber #F59E0B; LoginScreen правая половина = Lightning (WebGL, hue 35 тёплый) под стеклянной формой; левая панель — тёплый градиент. Доступ агентам: репо публичный, push german-токеном. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
154 lines
6.3 KiB
Bash
154 lines
6.3 KiB
Bash
#!/usr/bin/env bash
|
||
# ingress-watchdog.sh — алерт обрыва связи ингриса с MQTT-брокером (Milestone B)
|
||
# stdlib/bash only. Аддитивно. НЕ трогает приёмники/буферизацию/маршруты.
|
||
#
|
||
# Проба: TCP-connect к брокеру vm-mts1 10.50.0.48:1883 (таймаут 5с).
|
||
# Дебаунс: DOWN только после непрерывной недоступности > DOWN_THRESHOLD (60с).
|
||
# Алерт ТОЛЬКО на переходе состояния (up->down = ОБРЫВ, down->up = ВОССТАНОВЛЕНО).
|
||
# Каналы: всегда лог + статус-файл; Telegram — если в .env есть TG_TOKEN и TG_CHAT_ID.
|
||
|
||
set -u
|
||
|
||
# --- Конфиг (можно переопределить через .env) ---
|
||
BROKER_HOST="10.50.0.48"
|
||
BROKER_PORT="1883"
|
||
DOWN_THRESHOLD=60 # сек непрерывной недоступности до алерта ОБРЫВ
|
||
PROBE_TIMEOUT=5 # сек на TCP-пробу
|
||
STATE_FILE="/var/run/ingress-watchdog.state"
|
||
STATUS_FILE="/run/ingress-watchdog.status"
|
||
LOG_FILE="/var/log/ingress-watchdog.log"
|
||
ENV_FILE="/opt/ingress-watchdog/.env"
|
||
NODE_NAME="$(hostname)"
|
||
SPOOL_FILE="" # путь к queue.jsonl (задаётся в .env на каждой ноде)
|
||
ALERT_WEBHOOK="https://kpgen.servaki.online/webhook/alert" # единый n8n-хаб (можно переопределить в .env)
|
||
|
||
# --- Загрузка .env (TG_TOKEN, TG_CHAT_ID, SPOOL_FILE, NODE_NAME override) ---
|
||
if [ -f "$ENV_FILE" ]; then
|
||
# shellcheck disable=SC1090
|
||
. "$ENV_FILE"
|
||
fi
|
||
|
||
now="$(date +%s)"
|
||
ts="$(date '+%Y-%m-%d %H:%M:%S %Z')"
|
||
|
||
log() {
|
||
# НЕ логируем токен. Пишем строку в лог.
|
||
printf '%s [%s] %s\n' "$ts" "$NODE_NAME" "$1" >> "$LOG_FILE" 2>/dev/null
|
||
}
|
||
|
||
spool_count() {
|
||
if [ -n "${SPOOL_FILE:-}" ] && [ -f "$SPOOL_FILE" ]; then
|
||
wc -l < "$SPOOL_FILE" 2>/dev/null | tr -d ' '
|
||
else
|
||
echo 0
|
||
fi
|
||
}
|
||
|
||
send_telegram() {
|
||
# $1 = текст. Отправляем только если заданы token+chat_id.
|
||
local text="$1"
|
||
if [ -n "${TG_TOKEN:-}" ] && [ -n "${TG_CHAT_ID:-}" ]; then
|
||
timeout 10 curl -s -o /dev/null \
|
||
"https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
|
||
--data-urlencode "chat_id=${TG_CHAT_ID}" \
|
||
--data-urlencode "text=${text}" >/dev/null 2>&1 \
|
||
&& log "telegram: sent" \
|
||
|| log "telegram: send FAILED"
|
||
else
|
||
log "telegram: skipped (no token/chat_id)"
|
||
fi
|
||
}
|
||
|
||
send_webhook() {
|
||
# $1=severity (critical|warning|info), $2=title, $3=text. Единый хаб алертов (n8n).
|
||
local sev="$1" title="$2" text="$3" iso body
|
||
[ -z "${ALERT_WEBHOOK:-}" ] && { log "webhook: skipped (no ALERT_WEBHOOK)"; return; }
|
||
iso="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||
body="$(printf '{"source":"orbita-ingress","node":"%s","severity":"%s","title":"%s","text":"%s","ts":"%s"}' \
|
||
"$NODE_NAME" "$sev" "$title" "$text" "$iso")"
|
||
local code
|
||
code="$(timeout 12 curl -s -o /dev/null -m 10 --retry 2 -w '%{http_code}' \
|
||
-X POST "$ALERT_WEBHOOK" -H 'Content-Type: application/json' -d "$body" 2>/dev/null)"
|
||
if [ "$code" = "200" ]; then log "webhook: sent ($sev) HTTP 200"; else log "webhook: send FAILED (HTTP ${code:-000})"; fi
|
||
}
|
||
|
||
write_status() {
|
||
# $1=state up|down, $2=human message
|
||
{
|
||
printf 'node=%s\n' "$NODE_NAME"
|
||
printf 'state=%s\n' "$1"
|
||
printf 'updated=%s\n' "$ts"
|
||
printf 'updated_epoch=%s\n' "$now"
|
||
printf 'broker=%s:%s\n' "$BROKER_HOST" "$BROKER_PORT"
|
||
printf 'spool_lines=%s\n' "$(spool_count)"
|
||
printf 'last_message=%s\n' "$2"
|
||
} > "$STATUS_FILE" 2>/dev/null
|
||
}
|
||
|
||
# --- Читаем предыдущий стейт ---
|
||
last_ok=0
|
||
current_state="up"
|
||
down_since=0
|
||
if [ -f "$STATE_FILE" ]; then
|
||
# shellcheck disable=SC1090
|
||
. "$STATE_FILE" 2>/dev/null || true
|
||
fi
|
||
|
||
# --- TCP-проба ---
|
||
if timeout "$PROBE_TIMEOUT" bash -c "cat < /dev/null > /dev/tcp/${BROKER_HOST}/${BROKER_PORT}" 2>/dev/null; then
|
||
probe_ok=1
|
||
else
|
||
probe_ok=0
|
||
fi
|
||
|
||
new_state="$current_state"
|
||
|
||
if [ "$probe_ok" -eq 1 ]; then
|
||
last_ok="$now"
|
||
if [ "$current_state" = "down" ]; then
|
||
# ВОССТАНОВЛЕНО (переход down->up)
|
||
new_state="up"
|
||
down_since=0
|
||
spc="$(spool_count)"
|
||
msg="✅ ВОССТАНОВЛЕНО: связь ингриса ${NODE_NAME} с брокером ${BROKER_HOST}:${BROKER_PORT} восстановлена. В спуле сейчас: ${spc} сообщ. (дренаж до-дошлёт). Время: ${ts}"
|
||
log "STATE up (recovered). spool=${spc}"
|
||
write_status "up" "recovered"
|
||
send_webhook "info" "Связь восстановлена ${NODE_NAME}" "брокер ${BROKER_HOST}:${BROKER_PORT} снова доступен; в буфере ${spc} сообщ. (дренаж до-дошлёт)"
|
||
send_telegram "$msg"
|
||
else
|
||
# остаёмся up, без алерта
|
||
write_status "up" "ok"
|
||
fi
|
||
else
|
||
# недоступно
|
||
if [ "$down_since" -eq 0 ] 2>/dev/null || [ -z "${down_since:-}" ]; then
|
||
down_since="$now"
|
||
fi
|
||
down_for=$(( now - down_since ))
|
||
if [ "$current_state" = "up" ] && [ "$down_for" -gt "$DOWN_THRESHOLD" ]; then
|
||
# ОБРЫВ (переход up->down после дебаунса)
|
||
new_state="down"
|
||
spc="$(spool_count)"
|
||
msg="🔴 ОБРЫВ: ингрис ${NODE_NAME} потерял связь с брокером ${BROKER_HOST}:${BROKER_PORT} (недоступен ${down_for}с). В спуле накоплено: ${spc} сообщ. и буферизуется. Время: ${ts}"
|
||
log "STATE down (link lost ${down_for}s). spool=${spc}"
|
||
write_status "down" "link_lost"
|
||
send_webhook "critical" "Обрыв связи ${NODE_NAME}" "брокер ${BROKER_HOST}:${BROKER_PORT} недоступен ${down_for}с; в буфере ${spc} сообщ., буферизуется"
|
||
send_telegram "$msg"
|
||
elif [ "$current_state" = "down" ]; then
|
||
# уже в down — не спамим
|
||
write_status "down" "still_down"
|
||
else
|
||
# недоступно, но порог ещё не перевален — ждём
|
||
write_status "up" "probing_down ${down_for}s"
|
||
fi
|
||
fi
|
||
|
||
# --- Сохраняем стейт ---
|
||
{
|
||
printf 'last_ok=%s\n' "$last_ok"
|
||
printf 'current_state=%s\n' "$new_state"
|
||
printf 'down_since=%s\n' "$down_since"
|
||
} > "$STATE_FILE" 2>/dev/null
|
||
|
||
exit 0
|