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:
parent
a011d9e552
commit
f31ce95afe
6 changed files with 333 additions and 31 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ pub fn build(b: *std.Build) void {
|
|||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "service-monitor",
|
||||
.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_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
308
docs/PROYECTO_COMPLETO.md
Normal 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
|
||||
|
|
@ -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| {
|
||||
|
|
|
|||
22
src/http.zig
22
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue