From f31ce95afe9aa7a5ef05cc0b09babf8a13206d1a Mon Sep 17 00:00:00 2001 From: reugenio Date: Mon, 8 Dec 2025 01:03:43 +0100 Subject: [PATCH] =?UTF-8?q?Migraci=C3=B3n=20a=20Zig=200.15.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cambios principales: - build.zig: root_source_file → root_module con b.createModule() - stdout: std.io.getStdOut() → std.fs.File.stdout().deprecatedWriter() - ArrayList: std.ArrayList → std.array_list.Managed - file.reader(): deprecatedReader() para compatibilidad - HTTP Client: client.open/send/wait → client.fetch() - sleep: std.time.sleep → std.Thread.sleep Código funciona correctamente con Zig 0.15.2. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 4 +- build.zig | 16 +- docs/PROYECTO_COMPLETO.md | 308 ++++++++++++++++++++++++++++++++++++++ src/config.zig | 6 +- src/http.zig | 22 +-- src/main.zig | 8 +- 6 files changed, 333 insertions(+), 31 deletions(-) create mode 100644 docs/PROYECTO_COMPLETO.md diff --git a/CLAUDE.md b/CLAUDE.md index f25d283..8621e18 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,12 +1,12 @@ # service-monitor - Monitor de Servicios en Zig -> **Última actualización**: 2025-12-07 +> **Última actualización**: 2025-12-08 ## Descripción del Proyecto Monitor que verifica periódicamente que los servicios en nuestro servidor Hetzner (Simba) estén funcionando correctamente, con notificaciones si algo falla. -**Lenguaje**: Zig 0.13.0 +**Lenguaje**: Zig 0.15.2 **Objetivo**: Herramienta de monitoreo simple y ligera, sin dependencias de servicios externos como UptimeRobot. diff --git a/build.zig b/build.zig index 1e34cbf..1e37fcf 100644 --- a/build.zig +++ b/build.zig @@ -6,9 +6,11 @@ pub fn build(b: *std.Build) void { const exe = b.addExecutable(.{ .name = "service-monitor", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), }); // Instalar en carpeta raíz del proyecto (no en zig-out/) @@ -29,9 +31,11 @@ pub fn build(b: *std.Build) void { // Tests const unit_tests = b.addTest(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), }); const run_unit_tests = b.addRunArtifact(unit_tests); diff --git a/docs/PROYECTO_COMPLETO.md b/docs/PROYECTO_COMPLETO.md new file mode 100644 index 0000000..688a0cf --- /dev/null +++ b/docs/PROYECTO_COMPLETO.md @@ -0,0 +1,308 @@ +# Service Monitor - Documentación Completa + +> **Fecha**: 2025-12-08 +> **Versión**: 1.1 +> **Lenguaje**: Zig 0.15.2 +> **Repositorio**: https://git.reugenio.com/reugenio/service-monitor + +## Descripción + +Monitor de servicios HTTP y TCP para verificar disponibilidad del servidor Hetzner (Simba). Notifica por desktop, email y Telegram cuando hay servicios caídos. + +## Características Implementadas + +| Feature | Estado | Descripción | +|---------|--------|-------------| +| Verificación HTTP/HTTPS | ✅ | GET + status 200 + tiempo respuesta | +| Verificación TCP | ✅ | Conexión a puerto + DNS resolution | +| Modo watch | ✅ | Loop continuo con intervalo configurable | +| Modo daemon | ✅ | Background con fork() + setsid() | +| Log a archivo | ✅ | Append mode, timestamps ISO | +| Config externo | ✅ | Archivo CSV simple | +| Notificación desktop | ✅ | notify-send (Linux) | +| Notificación email | ✅ | SMTP con AUTH LOGIN (sin TLS) | +| Notificación Telegram | ✅ | Bot API via curl | + +## Estructura del Proyecto + +``` +service-monitor/ +├── build.zig # Sistema de build Zig +├── CLAUDE.md # Especificación del proyecto +├── services.conf # Configuración activa +├── services.conf.example # Ejemplo documentado +├── service-monitor # Binario compilado +├── service-monitor.log # Log de ejecución +├── service-monitor.pid # PID en modo daemon +│ +├── src/ +│ ├── main.zig # Entry point + CLI + lógica principal (317 líneas) +│ ├── http.zig # Cliente HTTP/HTTPS (77 líneas) +│ ├── tcp.zig # Verificación TCP (68 líneas) +│ ├── config.zig # Parser de configuración (233 líneas) +│ ├── notify.zig # Notificaciones desktop (77 líneas) +│ ├── daemon.zig # Daemonización (101 líneas) +│ ├── smtp.zig # Cliente SMTP (260 líneas) +│ └── telegram.zig # Cliente Telegram (107 líneas) +│ +└── docs/ + ├── DIARIO_DESARROLLO.md + └── PROYECTO_COMPLETO.md # Este archivo +``` + +## Uso + +### Comandos Básicos + +```bash +# Compilar +zig build + +# Verificar una vez +./service-monitor + +# Modo watch (cada 60 segundos) +./service-monitor --watch + +# Modo watch cada 30 segundos +./service-monitor -w -i 30 + +# Con log a archivo +./service-monitor -w -l + +# Con notificaciones desktop +./service-monitor -w -n + +# Daemon en background +./service-monitor -w -d + +# Todo junto +./service-monitor -w -i 60 -d -l -n + +# Config personalizado +./service-monitor -c /ruta/config.conf + +# Ayuda +./service-monitor --help +``` + +### Opciones CLI + +| Opción | Corto | Descripción | Default | +|--------|-------|-------------|---------| +| `--watch` | `-w` | Modo continuo | false | +| `--interval` | `-i` | Segundos entre checks | 60 | +| `--log` | `-l` | Log a archivo | service-monitor.log | +| `--notify` | `-n` | Notificaciones desktop | false | +| `--daemon` | `-d` | Ejecutar en background | false | +| `--config` | `-c` | Archivo de configuración | services.conf | +| `--help` | `-h` | Mostrar ayuda | - | + +### Gestión del Daemon + +```bash +# Iniciar daemon +./service-monitor -w -d -l -n + +# Ver PID +cat service-monitor.pid + +# Ver logs +tail -f service-monitor.log + +# Detener daemon +kill $(cat service-monitor.pid) +``` + +## Archivo de Configuración + +### Formato + +``` +# Comentarios con # +# tipo,parámetros... + +# Servicios HTTP +http,Nombre Servicio,https://url.com + +# Servicios TCP +tcp,Nombre Servicio,hostname,puerto + +# Email (destinatarios) +email,destinatario@ejemplo.com + +# Email (servidor SMTP) +email_smtp,smtp.servidor.com,puerto,usuario,password,from@email.com + +# Telegram +telegram,bot_token,chat_id +``` + +### Ejemplo Real (services.conf) + +``` +# Servicios HTTP +http,Forgejo (HTTP),https://git.reugenio.com +http,Simifactu API,https://simifactu.com +http,Mundisofa,https://mundisofa.com +http,Menzuri,https://menzuri.com + +# Servicios TCP +tcp,Forgejo (SSH),git.reugenio.com,2222 + +# Telegram +telegram,8158165444:AAFxUjLChsuusgFD5B1gt2svt8NflvAm1M8,1481345275 +``` + +## Servicios Monitoreados (Servidor Simba) + +| Servicio | Tipo | Endpoint | Puerto | +|----------|------|----------|--------| +| Forgejo HTTP | HTTPS | git.reugenio.com | 443 | +| Forgejo SSH | TCP | git.reugenio.com | 2222 | +| Simifactu | HTTPS | simifactu.com | 443 | +| Mundisofa | HTTPS | mundisofa.com | 443 | +| Menzuri | HTTPS | menzuri.com | 443 | + +## Output + +### Terminal (con colores) + +``` +[2025-12-07 20:53:24] +✓ Forgejo (HTTP) - OK (768ms) +✓ Forgejo (SSH) - OK (63ms) +✓ Simifactu API - OK (481ms) +✓ Mundisofa - OK (444ms) +✓ Menzuri - OK (456ms) +``` + +### Log (sin colores) + +``` +[2025-12-07 20:53:24] +OK Forgejo (HTTP) (768ms) +OK Forgejo (SSH) (63ms) +OK Simifactu API (481ms) +OK Mundisofa (444ms) +OK Menzuri (456ms) +``` + +### Telegram (cuando hay errores) + +``` +⚠️ ALERTA: Servicios caídos + +- Nombre Servicio 1 +- Nombre Servicio 2 +``` + +## Detalles Técnicos por Módulo + +### main.zig + +- Entry point de la aplicación +- Parser de argumentos CLI +- Lógica de loop watch/daemon +- Coordinación de todos los módulos +- Gestión de timestamps (UTC) + +### http.zig + +- Cliente HTTP usando `std.http.Client` +- Soporte HTTPS con TLS automático +- Buffer de headers: 8192 bytes +- Medición de tiempo de respuesta +- Errores tipados: DnsResolutionFailed, ConnectionFailed, Timeout, UnexpectedStatus + +### tcp.zig + +- Conexión TCP con `std.net.tcpConnectToHost` +- Resolución DNS automática +- Medición de tiempo de conexión +- Errores tipados: DnsResolutionFailed, ConnectionRefused, Timeout + +### config.zig + +- Parser CSV simple +- Soporte para comentarios (#) +- Estructuras: Service, SmtpConfig, TelegramConfig, Config +- Fallback a config por defecto si no existe archivo +- Gestión de memoria con allocator + +### notify.zig + +- Wrapper sobre `notify-send` +- Niveles de urgencia: low, normal, critical +- Funciones: send(), sendError(), sendRecovery() + +### daemon.zig + +- Double fork para daemonización correcta +- Syscall directo para setsid() (no disponible en std.posix Zig 0.13) +- Redirección de stdin/stdout/stderr a /dev/null +- Escritura de PID file +- Cambio a directorio raíz + +### smtp.zig + +- Protocolo SMTP completo (RFC 5321) +- Comandos: EHLO, AUTH LOGIN, MAIL FROM, RCPT TO, DATA, QUIT +- Autenticación Base64 +- Múltiples destinatarios +- **Limitación**: No soporta STARTTLS (usar puerto 25 o servidores sin TLS) + +### telegram.zig + +- Bot API via curl (más fiable que std.http para POST) +- Funciones: sendMessage(), sendAlert() +- Formato de mensaje con lista de servicios + +## Consumo de Recursos + +| Recurso | Valor | +|---------|-------| +| RAM | ~2-5 MB | +| CPU (idle) | ~0% | +| CPU (check) | <1% por ~2s | +| Binario | ~8.4 MB (debug) | +| Red | ~10KB por ciclo | + +## Commits Históricos + +| Hash | Descripción | +|------|-------------| +| e2e19da | Fase 1: Monitor básico HTTP/TCP | +| 3946f83 | Fase 2: Modo watch + CLI + timestamps | +| dfcfd31 | Log a archivo | +| 5a17d74 | Fase 3: Notificaciones desktop | +| 655dcb8 | Daemon mode + config externo | +| a011d9e | SMTP y Telegram | + +## Pendiente + +- [ ] SMTP con STARTTLS (para Gmail/Outlook) +- [x] ~~Migración a Zig 0.15~~ (completado 2025-12-08) +- [ ] Notificación de recuperación (servicio vuelve a funcionar) +- [ ] Rate limiting de notificaciones (evitar spam) +- [ ] Métricas/estadísticas de uptime + +## Notas de Migración a Zig 0.15 + +La migración a Zig 0.15.2 requirió los siguientes cambios: + +| Cambio | Antes (0.13) | Después (0.15) | +|--------|--------------|----------------| +| Build.zig | `root_source_file` | `root_module` con `b.createModule()` | +| stdout | `std.io.getStdOut().writer()` | `std.fs.File.stdout().deprecatedWriter()` | +| ArrayList | `std.ArrayList(T).init(alloc)` | `std.array_list.Managed(T).init(alloc)` | +| file.reader() | sin args | requiere buffer, usar `deprecatedReader()` | +| HTTP Client | `client.open()` + `send()` + `wait()` | `client.fetch()` | +| sleep | `std.time.sleep()` | `std.Thread.sleep()` | + +## Referencias + +- Zig 0.15 stdlib: https://ziglang.org/documentation/0.15.0/std/ +- SMTP RFC 5321: https://tools.ietf.org/html/rfc5321 +- Telegram Bot API: https://core.telegram.org/bots/api +- Guía Zig 0.15: /mnt/cello2/arno/re/recode/TEAM_STANDARDS/INFRASTRUCTURE/ZIG_0.15_GUIA.md diff --git a/src/config.zig b/src/config.zig index 51f2416..5df34d2 100644 --- a/src/config.zig +++ b/src/config.zig @@ -113,12 +113,12 @@ pub fn loadFromFile(allocator: std.mem.Allocator, path: []const u8) !Config { /// Parsea el contenido de un archivo de configuración. fn parseConfigFile(allocator: std.mem.Allocator, file: std.fs.File) !Config { - var services = std.ArrayList(Service).init(allocator); - var emails = std.ArrayList([]const u8).init(allocator); + var services = std.array_list.Managed(Service).init(allocator); + var emails = std.array_list.Managed([]const u8).init(allocator); var smtp = SmtpConfig{}; var telegram = TelegramConfig{}; - const reader = file.reader(); + const reader = file.deprecatedReader(); var buf: [1024]u8 = undefined; while (reader.readUntilDelimiterOrEof(&buf, '\n')) |maybe_line| { diff --git a/src/http.zig b/src/http.zig index 942c89a..1ed58b9 100644 --- a/src/http.zig +++ b/src/http.zig @@ -44,27 +44,17 @@ pub const HttpCheckError = error{ pub fn check(allocator: std.mem.Allocator, url: []const u8) HttpCheckError!u64 { var timer = std.time.Timer.start() catch return HttpCheckError.NetworkError; - // Parsear la URL - const uri = std.Uri.parse(url) catch return HttpCheckError.InvalidResponse; - - // Crear cliente HTTP - var client = std.http.Client{ .allocator = allocator }; + // Crear cliente HTTP (Zig 0.15 API) + var client: std.http.Client = .{ .allocator = allocator }; defer client.deinit(); - // Buffer para headers de respuesta (requerido en Zig 0.13) - var header_buffer: [8192]u8 = undefined; - - // Realizar la petición - API Zig 0.13 - var request = client.open(.GET, uri, .{ - .server_header_buffer = &header_buffer, + // Realizar la petición usando fetch() - API Zig 0.15 + const result = client.fetch(.{ + .location = .{ .url = url }, }) catch return HttpCheckError.ConnectionFailed; - defer request.deinit(); - - request.send() catch return HttpCheckError.NetworkError; - request.wait() catch return HttpCheckError.Timeout; // Verificar status - if (request.response.status != .ok) { + if (result.status != .ok) { return HttpCheckError.UnexpectedStatus; } diff --git a/src/main.zig b/src/main.zig index 187fee5..2d7c81a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -50,7 +50,7 @@ pub fn main() !void { defer _ = gpa.deinit(); const allocator = gpa.allocator(); - const stdout = std.io.getStdOut().writer(); + const stdout = std.fs.File.stdout().deprecatedWriter(); // Parsear argumentos const options = parseArgs() catch |err| { @@ -131,7 +131,7 @@ pub fn main() !void { while (true) { _ = try runChecks(allocator, output_writer, log_file, options.notify, &cfg); - std.time.sleep(@as(u64, options.interval_seconds) * std.time.ns_per_s); + std.Thread.sleep(@as(u64, options.interval_seconds) * std.time.ns_per_s); } } else { try stdout.print("\n=== Service Monitor ===\n\n", .{}); @@ -146,7 +146,7 @@ pub fn main() !void { /// Ejecuta verificación de todos los servicios. fn runChecks( allocator: std.mem.Allocator, - stdout: ?std.fs.File.Writer, + stdout: ?std.fs.File.DeprecatedWriter, log_file: ?std.fs.File, notify_enabled: bool, cfg: *const config.Config, @@ -181,7 +181,7 @@ fn runChecks( try out.print(timestamp_str ++ "\n", timestamp_args); } - const log_writer = if (log_file) |f| f.writer() else null; + const log_writer = if (log_file) |f| f.deprecatedWriter() else null; if (log_writer) |lw| { try lw.print(timestamp_str ++ "\n", timestamp_args); }