diff --git a/build.zig b/build.zig index a03c75c..1e34cbf 100644 --- a/build.zig +++ b/build.zig @@ -11,7 +11,10 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); - b.installArtifact(exe); + // Instalar en carpeta raíz del proyecto (no en zig-out/) + b.getInstallStep().dependOn(&b.addInstallArtifact(exe, .{ + .dest_dir = .{ .override = .{ .custom = ".." } }, + }).step); // Comando: zig build run const run_cmd = b.addRunArtifact(exe); diff --git a/src/main.zig b/src/main.zig index b56f229..a5c0a96 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5,14 +5,26 @@ //! dependencias externas. //! //! Uso: -//! zig build run - Verificar todos los servicios una vez -//! zig build run -- --help - Mostrar ayuda +//! zig build run - Verificar todos los servicios una vez +//! zig build run -- --watch - Modo continuo (cada 60s por defecto) +//! zig build run -- --watch -i 30 - Modo continuo cada 30 segundos +//! zig build run -- --help - Mostrar ayuda const std = @import("std"); const http = @import("http.zig"); const tcp = @import("tcp.zig"); const config = @import("config.zig"); +/// Opciones de línea de comandos. +const Options = struct { + /// Modo watch: ejecutar continuamente. + watch: bool = false, + /// Intervalo entre checks en segundos (solo en modo watch). + interval_seconds: u32 = 60, + /// Mostrar ayuda y salir. + help: bool = false, +}; + pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); @@ -20,10 +32,57 @@ pub fn main() !void { const stdout = std.io.getStdOut().writer(); - try stdout.print("\n=== Service Monitor ===\n\n", .{}); + // Parsear argumentos + const options = parseArgs() catch |err| { + try stdout.print("Error parseando argumentos: {}\n", .{err}); + try printUsage(stdout); + std.process.exit(1); + }; + if (options.help) { + try printUsage(stdout); + return; + } + + if (options.watch) { + // Modo watch: loop infinito + try stdout.print("\n=== Service Monitor (watch mode, interval: {d}s) ===\n", .{options.interval_seconds}); + try stdout.print("Presiona Ctrl+C para salir\n\n", .{}); + + while (true) { + const had_errors = try runChecks(allocator, stdout); + _ = had_errors; // En modo watch no salimos por errores + + std.time.sleep(@as(u64, options.interval_seconds) * std.time.ns_per_s); + } + } else { + // Modo único + try stdout.print("\n=== Service Monitor ===\n\n", .{}); + const had_errors = try runChecks(allocator, stdout); + + if (had_errors) { + std.process.exit(1); + } + } +} + +/// Ejecuta verificación de todos los servicios. +/// +/// Retorna true si hubo algún error, false si todos OK. +fn runChecks(allocator: std.mem.Allocator, stdout: anytype) !bool { const services = config.getServices(); - var all_ok = true; + var had_errors = false; + + // Timestamp + const timestamp = std.time.timestamp(); + const epoch_seconds: u64 = @intCast(timestamp); + const epoch = std.time.epoch.EpochSeconds{ .secs = epoch_seconds }; + const day_seconds = epoch.getDaySeconds(); + const hours = day_seconds.getHoursIntoDay(); + const minutes = day_seconds.getMinutesIntoHour(); + const seconds = day_seconds.getSecondsIntoMinute(); + + try stdout.print("[{d:0>2}:{d:0>2}:{d:0>2}]\n", .{ hours, minutes, seconds }); for (services) |service| { const result = switch (service.check_type) { @@ -34,14 +93,57 @@ pub fn main() !void { if (result) |time_ms| { try stdout.print("\x1b[32m✓\x1b[0m {s} - OK ({d}ms)\n", .{ service.name, time_ms }); } else |err| { - all_ok = false; + had_errors = true; try stdout.print("\x1b[31m✗\x1b[0m {s} - ERROR: {}\n", .{ service.name, err }); } } try stdout.print("\n", .{}); - - if (!all_ok) { - std.process.exit(1); - } + return had_errors; +} + +/// Parsea los argumentos de línea de comandos. +fn parseArgs() !Options { + var options = Options{}; + + var args = std.process.args(); + _ = args.skip(); // Saltar nombre del programa + + while (args.next()) |arg| { + if (std.mem.eql(u8, arg, "--watch") or std.mem.eql(u8, arg, "-w")) { + options.watch = true; + } else if (std.mem.eql(u8, arg, "--interval") or std.mem.eql(u8, arg, "-i")) { + const interval_str = args.next() orelse return error.MissingIntervalValue; + options.interval_seconds = std.fmt.parseInt(u32, interval_str, 10) catch { + return error.InvalidIntervalValue; + }; + } else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { + options.help = true; + } + } + + return options; +} + +/// Imprime mensaje de ayuda. +fn printUsage(stdout: anytype) !void { + try stdout.print( + \\ + \\Service Monitor - Verifica servicios HTTP y TCP + \\ + \\USO: + \\ service-monitor [opciones] + \\ + \\OPCIONES: + \\ --watch, -w Modo continuo (ejecuta checks en loop) + \\ --interval, -i Intervalo en segundos entre checks (default: 60) + \\ --help, -h Muestra esta ayuda + \\ + \\EJEMPLOS: + \\ service-monitor Verificar una vez + \\ service-monitor --watch Verificar cada 60 segundos + \\ service-monitor -w -i 30 Verificar cada 30 segundos + \\ + \\ + , .{}); }