This commit is contained in:
Lizandro Guarnizo
2026-05-21 23:49:37 -05:00
parent d80c7a86d3
commit 1a96caeb83
7 changed files with 324 additions and 13 deletions
+107 -1
View File
@@ -1,8 +1,11 @@
package services
import (
"encoding/json"
"fmt"
"log"
"strconv"
"time"
"github.com/robfig/cron/v3"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
@@ -32,8 +35,14 @@ func IniciarCron() {
return
}
// Salud de servidores — cada 5 minutos
if _, err := cronScheduler.AddFunc("*/5 * * * *", VerificarSaludServidores); err != nil {
log.Printf("[CRON] Error registrando tarea salud_servidores: %v", err)
return
}
cronScheduler.Start()
log.Println("[CRON] Scheduler iniciado — vencimientos próximos 8AM, ya vencidos 9AM, Bold polling cada 15min")
log.Println("[CRON] Scheduler iniciado — vencimientos próximos 8AM, ya vencidos 9AM, Bold polling cada 15min, salud servidores cada 5min")
}
// DetenerCron para graceful shutdown
@@ -208,3 +217,100 @@ func VerificarPagosBoldPendientes() {
log.Println("[BOLD-POLL] Verificación completada")
}
// ─── Salud de Servidores ─────────────────────────────────────────────────────
// VerificarSaludServidores corre cada 5 min y genera notificaciones del sistema
// cuando detecta: agente caído, recurso al límite o VPS próximo a vencer.
func VerificarSaludServidores() {
cfg := models.GetServidorAlertaUmbral()
if !cfg.Activo {
return
}
servidores, _, err := models.GetAllServidores(1000, 0, "")
if err != nil {
log.Printf("[CRON-SRV] Error obteniendo servidores: %v", err)
return
}
for _, srv := range servidores {
// 1. Agente caído (sin ping en X minutos)
if srv.AgentToken != "" && srv.AgentLastSeen != nil {
minutos := time.Since(*srv.AgentLastSeen).Minutes()
if minutos > float64(cfg.MinutosSinPing) {
titulo := fmt.Sprintf("🔴 Sin señal: %s", srv.Nombre)
if !models.YaExisteAlertaServidor(titulo, 1) {
crearNotifServidor(titulo,
fmt.Sprintf("El agente no reporta hace %.0f minutos (umbral: %d min)", minutos, cfg.MinutosSinPing),
"servidor_caido")
}
}
}
// 2. Recursos (CPU / RAM / Disco) al límite
if srv.MetricasJson != "" {
var m struct {
CPU struct{ Porcentaje float64 `json:"porcentaje"` } `json:"cpu"`
RAM struct{ Porcentaje float64 `json:"porcentaje"` } `json:"ram"`
Disco struct{ Porcentaje float64 `json:"porcentaje"` } `json:"disco"`
}
if jsonErr := json.Unmarshal([]byte(srv.MetricasJson), &m); jsonErr == nil {
checkRecurso(srv.Nombre, "CPU", m.CPU.Porcentaje, cfg.UmbralCPU)
checkRecurso(srv.Nombre, "RAM", m.RAM.Porcentaje, cfg.UmbralRAM)
checkRecurso(srv.Nombre, "Disco", m.Disco.Porcentaje, cfg.UmbralDisco)
}
}
// 3. Vencimiento próximo
if srv.Vencimiento != "" && cfg.DiasAnteVencimiento > 0 {
vence, parseErr := time.Parse("2006-01-02", srv.Vencimiento)
if parseErr == nil {
dias := int(time.Until(vence).Hours() / 24)
if dias >= 0 && dias <= cfg.DiasAnteVencimiento {
titulo := fmt.Sprintf("📅 %s vence en %s", srv.Nombre, strconv.Itoa(dias)+" días")
if dias == 0 {
titulo = fmt.Sprintf("📅 %s vence HOY", srv.Nombre)
}
if !models.YaExisteAlertaServidor(titulo, 12) {
crearNotifServidor(titulo,
fmt.Sprintf("Fecha de vencimiento: %s. Renueva a tiempo.", srv.Vencimiento),
"servidor_vence_pronto")
}
}
}
}
}
}
func checkRecurso(nombre, recurso string, valor float64, umbral int) {
if valor < float64(umbral) {
return
}
titulo := fmt.Sprintf("⚠️ %s alto en %s (%.0f%%)", recurso, nombre, valor)
if !models.YaExisteAlertaServidor(titulo, 1) {
crearNotifServidor(titulo,
fmt.Sprintf("%s al %.0f%% (umbral: %d%%)", recurso, valor, umbral),
"servidor_recurso_alto")
}
}
func crearNotifServidor(titulo, cuerpo, evento string) {
// Verificar si la notificación del sistema está habilitada para este evento
evtCfg := models.GetNotifConfig(evento, "admin")
// Por defecto, si no hay config, crear notificación de sistema igualmente
if evtCfg == nil || evtCfg.CanalSistema {
_ = models.CreateSistemaNotif(&models.SistemaNotificacion{
TipoUsuario: "admin",
Titulo: titulo,
Cuerpo: cuerpo,
Url: "/app/servidor-dashboard",
Icono: "🖥️",
})
}
// Telegram (si está configurado para este evento)
if evtCfg != nil && evtCfg.CanalTelegram {
msg := fmt.Sprintf("<b>%s</b>\n%s", titulo, cuerpo)
sendTelegramAdmin(msg)
}
}