Sei marzo 2026. Il sito fasolipiante.com è down. Nessun alert. Nessuna notifica. Nessuno dei quattro sistemi di monitoring in funzione — Uptime Kuma, xpilot-daemon, GlitchTip, il monitoraggio integrato di Cloudways — ha emesso un singolo segnale. Il sito semplicemente non era censito da nessuno di loro.
La scoperta ha innescato una revisione completa dell'architettura di monitoring. Il risultato: sessantuno monitor configurati in Uptime Kuma, un sistema di tier Gold/Silver/Carbon, e soprattutto un piano per risolvere il problema strutturale. Perché il vero problema non è mai stato l'assenza di un monitor — è che quattro sistemi operano come peer indipendenti, senza un cervello che li coordini.
Lo scenario attuale è questo: Uptime Kuma gira su Heimdall, un server Hetzner a Norimberga, e manda alert a Discord e Telegram per conto suo. GlitchTip, sullo stesso server, traccia errori applicativi e manda a Discord per conto suo. L'xpilot-daemon, un checker Python custom su Efesto, monitora tre siti (tre!) ogni trenta minuti e manda alert per conto suo. Cloudways manda email che probabilmente finiscono in spam. Signals, la dashboard TV su status.giobi.com, legge un file JSON di cache e mostra quello che c'è — passivamente, senza mai emettere un suono.
Il risultato è prevedibile: quando qualcosa cade in una fessura tra i sistemi, nessuno se ne accorge. Quando tutti e quattro vedono lo stesso problema, mandano quattro notifiche diverse nello stesso minuto. E quando la pipeline di cache si rompe, Signals mostra dati stale come se niente fosse.
La soluzione è una gerarchia chiara. Signals smette di essere una TV appesa al muro e diventa il centro di comando. I motori — Kuma per l'uptime, GlitchTip per gli errori, Beszel per le metriche server — restano dove sono e fanno il loro lavoro di raccolta dati. Ma non mandano più alert direttamente. Mandano tutto a Signals, e Signals decide cosa fare.
Visivamente è una piramide. In cima c'è Signals: orchestra, aggrega, alerta. Sotto ci sono i tre motori, tutti su Heimdall in Docker, ciascuno specializzato nel suo dominio. xpilot-daemon come HTTP checker muore — Kuma lo surclassa in tutto. Cloudways come fonte di alert viene ignorato — i suoi sessantuno siti WordPress sono già coperti da Kuma.
Questa architettura ha un vantaggio fondamentale: un singolo punto di decisione. Quando arriva un evento, c'è un solo sistema che decide se mandare un alert, a chi, con quale urgenza, e se aggregarlo con altri eventi correlati. Non quattro sistemi che agiscono indipendentemente.
Signals ha già un endpoint API autenticato per ricevere segnali dall'esterno. È il punto di ingresso naturale per tutti i motori.
Uptime Kuma supporta nativamente le notifiche via webhook. Si configura un notification type "webhook" che posta su Signals ad ogni cambio di stato — quando un monitor passa da UP a DOWN o viceversa. Il payload contiene l'ID del monitor, il nome, lo stato nuovo e precedente, l'eventuale codice HTTP o messaggio di errore, e il timestamp. Signals riceve e sa esattamente cosa è successo.
GlitchTip funziona allo stesso modo: ha un sistema di webhook notification che può postare su un URL esterno quando arrivano nuovi errori. Il payload include il progetto, il conteggio errori, l'issue principale, e il periodo. Signals riceve e classifica.
Il cron di cache — quello script Python che ogni cinque minuti raccoglie dati da Heimdall via SSH e scrive il file JSON — diventa una terza fonte. Alla fine di ogni esecuzione, se qualcosa è andato storto (un fetch fallito, un timeout SSH, dati mancanti), posta un signal di tipo "meta" direttamente sull'API. E se tutto è andato bene, pinga un push monitor Kuma dedicato. Se il cron salta, quel push monitor va in timeout, Kuma genera un evento DOWN, che arriva a Signals via webhook. Il watchdog ha un watchdog.
Quando un signal arriva a Signals, la prima cosa che succede è la classificazione. Ogni monitor ha un tier assegnato: Gold per i clienti paganti e i servizi critici propri, Silver per amici e progetti personali, Carbon per staging e tool interni. Il router fa un lookup — monitor ID o nome → tier — e attacca la classificazione al signal.
Questo mapping esiste già implicitamente nella configurazione dei sessantuno monitor Kuma, dove Gold ha intervallo di sessanta secondi, Silver centoventi, Carbon trecento. Basta esportarlo una volta in una tabella o un file JSON che Signals consulta.
La classificazione determina tutto quello che segue: dove mandare l'alert, con quale urgenza, se escalare, come aggregare. Un Gold DOWN è un'emergenza. Un Carbon DOWN è un'informazione.
Se Kuma manda un evento DOWN per Fasoli Piante, e sessanta secondi dopo manda un altro DOWN per lo stesso monitor perché il retry è fallito, Signals non deve mandare due alert. La regola è semplice: stesso monitor, stesso stato, entro cinque minuti — è un duplicato. Il signal esistente viene aggiornato (ultimo check, conteggio retry), ma non viene emesso un nuovo alert.
L'implementazione è una query prima dell'inserimento. Se esiste già un signal con lo stesso source, monitor ID, e stato, creato negli ultimi cinque minuti, si fa update invece di insert. Solo i signal nuovi — quelli che rappresentano un vero cambio di stato — triggerano il dispatch degli alert.
Questo è il caso più interessante. Immagina che il server Octopus su Cloudways vada giù. In quel momento, quindici siti WordPress ospitati lì diventano irraggiungibili. Kuma manda quindici webhook DOWN in rapida successione. Senza aggregazione, ricevi quindici notifiche Telegram in trenta secondi — un'esperienza orribile che ti fa silenziare il canale, che è esattamente l'opposto di quello che vuoi.
Il router tiene un buffer di sessanta secondi. Quando arriva il primo DOWN, invece di mandare subito l'alert, schedula un job Laravel delayed di sessanta secondi. Il job, quando si attiva, controlla quanti DOWN sono arrivati nel frattempo. Se sono più di tre, li aggrega in un unico alert digest: "quindici monitor DOWN, dodici Gold e tre Silver, probabile problema server Octopus". Se sono tre o meno, manda i singoli alert normalmente.
Il buffer di sessanta secondi è un compromesso. Abbastanza lungo per catturare un'ondata di failure correlati. Abbastanza corto per non ritardare troppo l'alert di un singolo monitor Gold. Se un giorno servirà più sofisticazione — correlazione per server, per subnet, per provider — il punto di intervento è chiaro: il job di aggregazione.
Un Gold è DOWN da cinque minuti e nessuno ha fatto ACK sulla dashboard di Signals. Significa che nessuno ha visto l'alert, o nessuno ha reagito. Il router entra in modalità escalation.
Un comando Laravel schedulato gira ogni minuto. Cerca signal di tipo uptime con stato DOWN, tier Gold, creati più di cinque minuti fa, senza acknowledged_at e senza escalated_at. Per ognuno, manda un secondo alert — più aggressivo nel tono, esplicitamente etichettato come escalation, con il tempo di downtime accumulato. Poi segna escalated_at per non ri-escalare.
L'escalation è il safety net. Se il primo alert non funziona — perché eri al bagno, perché Discord era mutato, perché il telefono era in silenzioso — il secondo arriva su Telegram con un tono che dice "ehi, è passato del tempo, questa roba è ancora rotta". In futuro, un terzo livello potrebbe essere un SMS o una telefonata automatica. Ma per ora, due livelli bastano.
Il router decide i canali, poi delega al dispatcher. Il dispatcher è semplice: prende un signal e una lista di canali target, e per ciascuno formatta e manda il messaggio.
Discord riceve messaggi più verbosi — è un canale che leggi con calma, scrolli, cerchi. Il formato include nome del monitor, tier, URL, status code o errore, timestamp, e un link alla dashboard Signals. Telegram riceve messaggi concisi — è una push notification che leggi di sfuggita sul telefono. Due righe: cosa è down e perché.
Il punto importante è che i template dei messaggi sono in un posto solo: dentro Signals. Oggi i messaggi di Kuma usano il template di Kuma, quelli di GlitchTip il template di GlitchTip, e il formato è diverso per ciascuno. Con il dispatcher centralizzato, tutti gli alert hanno lo stesso formato, la stessa struttura, lo stesso livello di informazione. Quando leggi una notifica sai immediatamente cosa stai guardando indipendentemente dalla fonte.
Il dispatcher usa i wrapper Python esistenti nel brain — discord.py e telegram.py — chiamati via shell exec o HTTP interno. Niente di nuovo da costruire per il delivery.
La migrazione non è un big bang. È graduale, e ogni passo è reversibile.
Step uno: doppio binario. Aggiungi la notification webhook a Kuma, puntata verso Signals. Ma non toccare le notifiche esistenti — Kuma continua a mandare a Discord e Telegram direttamente. Signals riceve i webhook e li logga nel database, senza fare altro. Questo permette di verificare che il flusso di dati funziona, che i payload sono corretti, che non ci sono problemi di autenticazione o timeout. Zero rischio: se Signals si rompe, gli alert diretti di Kuma funzionano come prima.
Step due: alert paralleli. Signals inizia a mandare i suoi alert — con dedup, aggregazione, escalation. Per un periodo avrai doppi alert: quelli diretti di Kuma e quelli di Signals. Fastidioso, ma è l'unico modo per verificare che Signals copra tutti i casi. Confronti: l'alert di Signals è arrivato? In tempo? Il formato è chiaro? L'aggregazione funziona? Se qualcosa non va, fix in Signals senza mai toccare il flusso di Kuma.
Step tre: taglio. Quando sei soddisfatto che Signals funziona, rimuovi Discord e Telegram dalle notification di Kuma. Kuma manda solo il webhook a Signals. Signals è l'unico router. Stesso procedimento per GlitchTip.
Step quattro: pulizia. Disabilita il cron di xpilot-daemon per gli HTTP check. Valuta se tenerlo per check specialistici futuri (form POST, content validation) o spegnerlo del tutto. Rimuovi qualsiasi riferimento a Cloudways come fonte di alert.
Se in qualsiasi momento Signals si rompe o il routing non funziona come atteso, il rollback è banale: riattivi le notifiche dirette in Kuma. Due minuti.
Non costruire un message queue. Redis, RabbitMQ, Kafka — tutta roba che aggiunge complessità operativa per un sistema che gestisce, nella peggiore delle ipotesi, qualche centinaio di eventi al giorno. I Laravel jobs con database driver bastano e avanzano. Se un giorno il volume cresce di tre ordini di grandezza, si rivaluta. Ma quel giorno non è oggi.
Non toccare il codice interno di Kuma. Il webhook è l'interfaccia giusta — standard, pulita, documentata. Forkare Kuma per aggiungere logica custom significa ereditare la manutenzione di un progetto Node.js complesso per un beneficio marginale. Webhook in, webhook out.
Non creare un microservice separato per il router. È un controller Laravel nuovo, due service class (AlertRouter e AlertDispatcher), un comando schedulato per l'escalation, e una migration per aggiungere tre campi al model Signal. Forse trecento righe di PHP in tutto. Inserirlo in Signals — che è già un'app Laravel su Efesto — è la scelta ovvia. Niente nuovi deployment, niente nuovi domini, niente nuova infrastruttura.
Non mettere logica di routing nel cron di cache. Il cron fa una cosa sola: prende dati da Heimdall e li scrive in un JSON. Il routing è responsabilità di Signals. Separazione delle preoccupazioni — ognuno fa il suo mestiere.
In termini di codice: un webhook notification nuovo in Kuma (configurazione web, cinque minuti), un controller in Signals per ricevere i webhook e instradarli al router (cinquanta righe), il servizio AlertRouter con classificazione, dedup, aggregazione e regole (centocinquanta righe), il servizio AlertDispatcher con template Discord e Telegram (cento righe), un comando schedulato per l'escalation (cinquanta righe), e una migration per tier, acknowledged_at, escalated_at nel model Signal (venti righe). Totale: circa trecento righe di PHP applicativo.
In termini di rischio: quasi zero, grazie al piano di migrazione graduale. In ogni momento del percorso, il sistema funziona — prima con doppi alert (ridondante ma sicuro), poi con Signals come unico router.
In termini di beneficio: un singolo punto di decisione per tutti gli alert. Niente più "quattro sistemi e nessuno mi dice un cazzo". Un solo posto dove guardare, un solo posto da debuggare, un solo posto dove cambiare le regole. Se domani vuoi aggiungere SMS, aggiungi un canale al dispatcher. Se vuoi cambiare i tier, aggiorni il mapping. Se vuoi silenzare gli alert durante una maintenance window, aggiungi un flag. Tutto in un posto.
È la differenza tra un'orchestra con un direttore e quattro musicisti che suonano ciascuno per conto suo. La musica può anche essere la stessa, ma il risultato è radicalmente diverso.