main.zig
1 const std = @import("std"); 2 const zap = @import("zap"); 3 const oss = @import("os-stats"); 4 const utils = @import("utils"); 5 6 const os = std.os; 7 const print = std.debug.print; 8 const mem = std.mem; 9 const fmt = std.fmt; 10 const json = std.json; 11 12 const CPUInfo = oss.CPUInfo; 13 const RAMStat = oss.RAMStat; 14 const OSInfo = oss.OSInfo; 15 16 const str = []const u8; 17 const banner = @embedFile("banner"); 18 19 const DEFAULT_PORT = 3040; 20 const DEFAULT_PASSWORD = "admin"; 21 const DEFAULT_SERVERNAME = "ZDSM"; 22 23 var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 24 var alloc = gpa.allocator(); 25 var ctx: *const Context = undefined; 26 27 // TODO: Parse build.zig.zon at compile time to get version 28 const SERVER_VERSION = "v0.1.0"; 29 30 const SoftwareInfo = struct { version: str = SERVER_VERSION }; 31 32 const ServerInfo = struct { 33 id: str, 34 uptime: i64, 35 hostname: str, 36 cpu: ?CPUInfo, 37 ram: ?RAMStat, 38 os: ?OSInfo, 39 }; 40 41 const Infos = struct { software: SoftwareInfo, server: ServerInfo }; 42 43 const Context = struct { 44 server_name: str, 45 password: str, 46 }; 47 48 fn getLoadAverage() str { 49 // https://fr.wikipedia.org/wiki/Load_average 50 return "TODO"; 51 } 52 53 fn trimZerosRight(value: *[64:0]u8) []u8 { 54 return value[0..mem.indexOfScalar(u8, value, 0).?]; 55 } 56 57 fn processRequest(request: zap.Request) void { 58 if (!(request.method == null) and !mem.eql(u8, request.method.?, "GET") or (!(request.path == null) and !mem.eql(u8, request.path.?, "/api"))) { 59 request.setStatus(.not_found); 60 request.sendJson("{\"Error\":\"BAD REQUEST\"}") catch return; 61 utils.warn("Got malformed request: got {?s} /{?s}", .{ request.method, request.path }) catch return; 62 return; 63 } 64 65 const authenticator = zap.Auth.BearerSingle; 66 var auth = authenticator.init(alloc, ctx.password, null) catch return; 67 defer auth.deinit(); 68 69 const rq = request; 70 const ar = auth.authenticateRequest(&rq); 71 72 if (ar != zap.Auth.AuthResult.AuthOK) { 73 request.setStatus(.unauthorized); 74 request.sendJson("{\"Error\":\"UNAUTHORIZED\"}") catch return; 75 utils.warn("Login attempt failed", .{}) catch return; 76 return; 77 } 78 79 var request_body: []const u8 = undefined; 80 var cpu_info_buff: [256]u8 = undefined; 81 var uname = os.uname(); 82 83 const system_info = Infos{ 84 .software = SoftwareInfo{}, 85 .server = ServerInfo{ 86 .id = ctx.server_name, 87 .uptime = oss.getUptime(), 88 .hostname = trimZerosRight(&uname.nodename), 89 .cpu = CPUInfo{ 90 .usage = oss.getCPUPercent(null) orelse 0, 91 .arch = trimZerosRight(&uname.machine), 92 .model = utils.parseKVPairOpenFile("/proc/cpuinfo", "model name", &cpu_info_buff, ':') catch return orelse "Data Unavailable", 93 }, 94 .ram = oss.getRAMStats(), 95 .os = OSInfo{ 96 .type = trimZerosRight(&uname.sysname), 97 .platform = trimZerosRight(&uname.sysname), // basically the same as the OS type 98 .version = trimZerosRight(&uname.version), 99 .release = trimZerosRight(&uname.release), 100 }, 101 }, 102 }; 103 104 request_body = json.stringifyAlloc(alloc, system_info, .{}) catch "{\"Error\":\"Unable to generate JSON\"}"; 105 request.sendJson(request_body) catch return; 106 } 107 108 pub fn main() !void { 109 var password_buff: [50]u8 = undefined; 110 var server_name_buff: [50]u8 = undefined; 111 var port_buffer: [5]u8 = undefined; 112 113 const password = utils.getenv("PASSWORD", &password_buff) orelse DEFAULT_PASSWORD; 114 const server_name = utils.getenv("SERVER_NAME", &server_name_buff) orelse DEFAULT_SERVERNAME; 115 const port = p: { 116 const env = utils.getenv("PORT", &port_buffer); 117 if (env == null) break :p DEFAULT_PORT; 118 break :p fmt.parseUnsigned(usize, env.?, 10) catch DEFAULT_PORT; 119 }; 120 121 ctx = &Context{ .server_name = server_name, .password = password }; 122 123 var server = zap.HttpListener.init(.{ 124 .port = port, 125 .on_request = processRequest, 126 .log = false, 127 }); 128 129 print(banner, .{ server_name, SERVER_VERSION, port, password }); 130 131 try server.listen(); 132 try utils.info("Server running on port {any}", .{@as(u16, @truncate(port))}); 133 134 zap.start(.{ 135 .threads = 1, 136 .workers = 1, 137 }); 138 }