#!/usr/bin/env bash # # ПРОД food-market: мониторинг здоровья. Проверяет api/web/БД, место на диске и # число 5xx в логах api за последний интервал. При проблеме шлёт владельцу в # Telegram (бот уже есть; token/chat — ~/.fm-watchdog/telegram-{token,chat}). # # Запуск из systemd-таймера (каждые ~5 мин). Аргументы: # (без аргументов) — обычная проверка, уведомляет ТОЛЬКО при проблеме. # --test — отправляет тестовое уведомление «мониторинг жив» и выходит. # --heartbeat — всегда шлёт короткий статус (для ручной проверки). set -uo pipefail WATCH_DIR="${WATCH_DIR:-$HOME/.fm-watchdog}" API_URL="${FM_API_URL:-http://localhost:8080/health/ready}" WEB_URL="${FM_WEB_URL:-http://localhost:8081/}" PUBLIC_URL="${FM_PUBLIC_HEALTH:-https://admin.food-market.kz/health/ready}" CONTAINER="${FM_PG_CONTAINER:-food-market-prod-postgres}" API_CONTAINER="${FM_API_CONTAINER:-food-market-api}" DISK_PATH="${FM_DISK_PATH:-/opt/food-market-data}" DISK_WARN_PCT="${FM_DISK_WARN_PCT:-85}" LOG="${FM_MONITOR_LOG:-$WATCH_DIR/prod-monitor.log}" log() { echo "[$(date -Is)] $*" | tee -a "$LOG" >/dev/null; } notify() { local msg="$1" local token chat token="$(cat "$WATCH_DIR/telegram-token" 2>/dev/null || true)" chat="$(cat "$WATCH_DIR/telegram-chat" 2>/dev/null || true)" if [[ -z "$token" || -z "$chat" ]]; then log "telegram token/chat не настроены — notify пропущен: $msg"; return fi curl -sS --max-time 10 -X POST "https://api.telegram.org/bot$token/sendMessage" \ --data-urlencode "chat_id=$chat" \ --data-urlencode "text=$msg" >/dev/null 2>&1 \ && log "notify отправлено: $msg" || log "notify FAILED: $msg" } if [[ "${1:-}" == "--test" ]]; then notify "✅ ALL MARKET прод: тест мониторинга $(date '+%Y-%m-%d %H:%M'). Если видите это — уведомления работают." exit 0 fi problems=() # API /health/ready if ! curl -fsS --max-time 10 "$API_URL" 2>/dev/null | grep -q '"status":"Healthy"'; then problems+=("API /health/ready не Healthy ($API_URL)") fi # Web SPA code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$WEB_URL" 2>/dev/null || echo 000) [[ "$code" =~ ^(200|301|302)$ ]] || problems+=("web вернул HTTP $code ($WEB_URL)") # Публичный вход (через gateway) pcode=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$PUBLIC_URL" 2>/dev/null || echo 000) [[ "$pcode" =~ ^(200)$ ]] || problems+=("публичный $PUBLIC_URL → HTTP $pcode") # БД if ! docker exec "$CONTAINER" pg_isready -U food_market -d food_market >/dev/null 2>&1; then problems+=("БД $CONTAINER не отвечает (pg_isready)") fi # Диск pct=$(df --output=pcent "$DISK_PATH" 2>/dev/null | tail -1 | tr -dc '0-9') if [[ -n "$pct" && "$pct" -ge "$DISK_WARN_PCT" ]]; then problems+=("диск $DISK_PATH заполнен на ${pct}% (порог ${DISK_WARN_PCT}%)") fi # 5xx в логах api за последние 5 минут fivexx=$(docker logs "$API_CONTAINER" --since 5m 2>&1 | grep -cE ' (50[0-9]) ' || true) if [[ -n "$fivexx" && "$fivexx" -gt 0 ]]; then problems+=("$fivexx ответов 5xx в логах api за 5 мин") fi if [[ ${#problems[@]} -gt 0 ]]; then msg="🔴 ALL MARKET прод — проблемы:"$'\n'"$(printf -- '- %s\n' "${problems[@]}")" log "ПРОБЛЕМЫ: ${problems[*]}" notify "$msg" exit 1 fi log "OK: api/web/public/БД/диск(${pct}%)/5xx=0" [[ "${1:-}" == "--heartbeat" ]] && notify "🟢 ALL MARKET прод OK $(date '+%H:%M') (диск ${pct}%)" exit 0