Migración a Zig 0.15.2

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 <noreply@anthropic.com>
This commit is contained in:
reugenio 2025-12-08 01:03:43 +01:00
parent a011d9e552
commit f31ce95afe
6 changed files with 333 additions and 31 deletions

View file

@ -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.

View file

@ -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);

308
docs/PROYECTO_COMPLETO.md Normal file
View file

@ -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

View file

@ -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| {

View file

@ -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;
}

View file

@ -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);
}