Perché un bot Telegram?

Quando gestisci un server in produzione, la cosa peggiore è scoprire che qualcosa è andato storto ore dopo che è successo. Email di alert? Finiscono nello spam. PagerDuty? Overkill per un progetto personale. Un bot Telegram invece è gratuito, arriva sul telefono in tempo reale e si configura in 10 minuti.

In questo articolo costruiamo un sistema che:


1. Creare il bot su Telegram

Prima di tutto crea il bot tramite @BotFather:

  1. Apri Telegram e cerca @BotFather
  2. Manda /newbot
  3. Scegli un nome e un username (es. miosito_monitor_bot)
  4. Salva il token API che ti restituisce, tipo: 7123456789:AAHxxx...

Poi ottieni il tuo chat_id: manda un messaggio al bot e visita:

https://api.telegram.org/bot<TOKEN>/getUpdates

Nel JSON risposta trovi "chat":{"id":123456789} — quel numero è il tuo chat_id.


2. La funzione di invio messaggio

Creiamo il file base /usr/local/bin/tg-notify.sh:

#!/bin/bash

TG_TOKEN="7123456789:AAHxxx..."
TG_CHAT_ID="123456789"

tg_send() {
  local message="$1"
  curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
    -d chat_id="${TG_CHAT_ID}" \
    -d parse_mode="HTML" \
    -d text="${message}" \
    > /dev/null
}

Usiamo parse_mode=HTML così possiamo formattare i messaggi con <b>, <code> ecc.


3. Monitor dell'error log PHP

Il trick è usare tail -Fn0 per seguire il file e rilevare solo le nuove righe dall'avvio del monitor:

#!/bin/bash
source /usr/local/bin/tg-notify.sh

PHP_LOG="/var/log/php/error.log"
HOSTNAME=$(hostname)

monitor_php_log() {
  tail -Fn0 "$PHP_LOG" | while read -r line; do
    # Filtra solo righe con errori reali (ignora notice/deprecated in dev)
    if echo "$line" | grep -qiE "(Fatal error|Parse error|Warning|Uncaught)"; then
      tg_send "🔴 <b>PHP Error — ${HOSTNAME}</b>
<code>${line}</code>"
    fi
  done
}

monitor_php_log

Tip: Se vuoi evitare flood di notifiche, aggiungi un rate limit: salva l'ultimo timestamp di invio e skippa se sono passati meno di 60 secondi per lo stesso tipo di errore.


4. Monitor dei servizi HTTP e porte TCP

#!/bin/bash
source /usr/local/bin/tg-notify.sh

HOSTNAME=$(hostname)
STATE_DIR="/tmp/tg-monitor-state"
mkdir -p "$STATE_DIR"

check_http() {
  local name="$1"
  local url="$2"
  local state_file="${STATE_DIR}/http_${name}"

  local code
  code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$url")

  if [ "$code" -lt 200 ] || [ "$code" -ge 400 ]; then
    # Invia alert solo se non era già down (evita spam)
    if [ ! -f "$state_file" ]; then
      touch "$state_file"
      tg_send "🔴 <b>DOWN: ${name}</b>
Host: <code>${HOSTNAME}</code>
URL: <code>${url}</code>
HTTP: <code>${code}</code>"
    fi
  else
    # Era down, ora è tornato su
    if [ -f "$state_file" ]; then
      rm -f "$state_file"
      tg_send "✅ <b>RECOVERED: ${name}</b>
Host: <code>${HOSTNAME}</code>
URL: <code>${url}</code>"
    fi
  fi
}

check_tcp() {
  local name="$1"
  local host="$2"
  local port="$3"
  local state_file="${STATE_DIR}/tcp_${name}"

  if ! nc -z -w5 "$host" "$port" 2>/dev/null; then
    if [ ! -f "$state_file" ]; then
      touch "$state_file"
      tg_send "🔴 <b>DOWN: ${name}</b>
Host: <code>${host}:${port}</code>"
    fi
  else
    if [ -f "$state_file" ]; then
      rm -f "$state_file"
      tg_send "✅ <b>RECOVERED: ${name}</b>
Host: <code>${host}:${port}</code>"
    fi
  fi
}

# ── Configura qui i tuoi servizi ─────────────────────────────
check_http  "sito principale"  "https://donatodelpeschio.it"
check_http  "api"              "https://api.donatodelpeschio.it/health"
check_tcp   "PostgreSQL"       "localhost" "5432"
check_tcp   "Redis"            "localhost" "6379"

Il sistema dei state file in /tmp/tg-monitor-state/ è fondamentale: evita che tu riceva 50 notifiche "DOWN" ogni minuto per lo stesso servizio. Ricevi un alert quando cade e uno quando torna su.


5. Orchestrare tutto con systemd (meglio del cron)

Per il monitor del log PHP, il cron non va bene perché usa tail -f. Usiamo systemd:

# /etc/systemd/system/php-log-monitor.service
[Unit]
Description=PHP Error Log Telegram Monitor
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/php-log-monitor.sh
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
systemctl enable --now php-log-monitor

Per il monitor dei servizi HTTP/TCP invece il cron è perfetto (ogni minuto):

# crontab -e
* * * * * /usr/local/bin/service-monitor.sh

6. Bonus: report giornaliero

Ogni mattina alle 9:00 ricevi un riepilogo dello stato dei servizi:

#!/bin/bash
source /usr/local/bin/tg-notify.sh

UPTIME=$(uptime -p)
DISK=$(df -h / | awk 'NR==2 {print $3"/"$2" ("$5")"}')
MEM=$(free -h | awk 'NR==2 {print $3"/"$2}')
LOAD=$(uptime | awk -F'load average:' '{print $2}')

tg_send "📊 <b>Daily Report</b>
🖥 <code>$(hostname)</code>
⏱ Uptime: <code>${UPTIME}</code>
💾 Disk: <code>${DISK}</code>
🧠 RAM: <code>${MEM}</code>
⚡ Load: <code>${LOAD}</code>"
# crontab
0 9 * * * /usr/local/bin/daily-report.sh

Risultato finale

Con meno di 100 righe di bash hai un sistema di monitoring che:

Il prossimo step? Aggiungere un comando /status al bot per interrogare lo stato dei servizi on-demand. Ma questo è materiale per un altro articolo. 🚀