// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) // // Francis Bouvier // Pierre Tachoire // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . const std = @import("std"); const lp = @import("lightpanda"); const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const log = lp.log; const App = lp.App; const Config = lp.Config; const SigHandler = @import("Sighandler.zig"); pub const panic = lp.crash_handler.panic; pub fn main() !void { // allocator // - in Debug mode we use the General Purpose Allocator to detect memory leaks // - in Release mode we use the c allocator var gpa_instance: std.heap.DebugAllocator(.{}) = .init; const gpa = if (builtin.mode == .Debug) gpa_instance.allocator() else std.heap.c_allocator; defer if (builtin.mode == .Debug) { if (gpa_instance.detectLeaks()) std.posix.exit(1); }; // arena for main-specific allocations var main_arena_instance = std.heap.ArenaAllocator.init(gpa); const main_arena = main_arena_instance.allocator(); defer main_arena_instance.deinit(); var sighandler = SigHandler{ .arena = main_arena }; try sighandler.install(); run(gpa, main_arena, &sighandler) catch |err| { log.fatal(.app, "exit", .{ .err = err }); std.posix.exit(1); }; } fn run(allocator: Allocator, main_arena: Allocator, sighandler: *SigHandler) !void { const args = try Config.parseArgs(main_arena); defer args.deinit(main_arena); switch (args.mode) { .help => { args.printUsageAndExit(args.mode.help); return std.process.cleanExit(); }, .version => { std.debug.print("{s}\n", .{lp.build_config.git_commit}); return std.process.cleanExit(); }, else => {}, } if (args.logLevel()) |ll| { log.opts.level = ll; } if (args.logFormat()) |lf| { log.opts.format = lf; } if (args.logFilterScopes()) |lfs| { log.opts.filter_scopes = lfs; } // _app is global to handle graceful shutdown. var app = try App.init(allocator, &args); defer app.deinit(); app.telemetry.record(.{ .run = {} }); switch (args.mode) { .serve => |opts| { log.debug(.app, "startup", .{ .mode = "serve", .snapshot = app.snapshot.fromEmbedded() }); const address = std.net.Address.parseIp(opts.host, opts.port) catch |err| { log.fatal(.app, "invalid server address", .{ .err = err, .host = opts.host, .port = opts.port }); return args.printUsageAndExit(false); }; // _server is global to handle graceful shutdown. var server = try lp.Server.init(allocator, app, address); defer server.deinit(); try sighandler.on(lp.Server.stop, .{&server}); // max timeout of 1 week. const timeout = if (opts.timeout > 604_800) 604_800_000 else @as(u32, opts.timeout) * 1000; server.run(address, timeout) catch |err| { log.fatal(.app, "server run error", .{ .err = err }); return err; }; }, .fetch => |opts| { const url = opts.url; log.debug(.app, "startup", .{ .mode = "fetch", .dump = opts.dump, .url = url, .snapshot = app.snapshot.fromEmbedded() }); var fetch_opts = lp.FetchOpts{ .wait_ms = 5000, .dump = .{ .strip = opts.strip, .with_base = opts.withbase, }, }; var stdout = std.fs.File.stdout(); var writer = stdout.writer(&.{}); if (opts.dump) { fetch_opts.writer = &writer.interface; } lp.fetch(allocator, app, url, fetch_opts) catch |err| { log.fatal(.app, "fetch error", .{ .err = err, .url = url }); return err; }; }, else => unreachable, } }