const std = @import("std"); const builtin = @import("builtin"); const sql = @import("sql"); const http = @import("http"); const util = @import("util"); const api = @import("api"); pub const migrations = @import("./migrations.zig"); // TODO const c = @import("./controllers.zig"); pub const Config = struct { worker_threads: usize = 10, db: sql.Config, }; fn loadConfig(alloc: std.mem.Allocator) !Config { var file = try std.fs.cwd().openFile("config.json", .{}); defer file.close(); const bytes = try file.reader().readAllAlloc(alloc, 1 << 63); defer alloc.free(bytes); var ts = std.json.TokenStream.init(bytes); return std.json.parse(Config, &ts, .{ .allocator = alloc }); } const admin_origin_envvar = "CLUSTER_ADMIN_ORIGIN"; const admin_username_envvar = "CLUSTER_ADMIN_USERNAME"; const admin_password_envvar = "CLUSTER_ADMIN_PASSWORD"; fn runAdminSetup(db: sql.Db, alloc: std.mem.Allocator) !void { const origin = std.os.getenv(admin_origin_envvar) orelse return error.MissingArgument; const username = std.os.getenv(admin_username_envvar) orelse return error.MissingArgument; const password = std.os.getenv(admin_password_envvar) orelse return error.MissingArgument; try api.setupAdmin(db, origin, username, password, alloc); } fn prepareDb(pool: *sql.ConnPool, alloc: std.mem.Allocator) !void { const db = try pool.acquire(); defer db.releaseConnection(); try migrations.up(db); if (!try api.isAdminSetup(db)) { std.log.info("Performing first-time admin creation...", .{}); runAdminSetup(db, alloc) catch |err| switch (err) { error.MissingArgument => { std.log.err( \\First time setup required but arguments not provided. \\Please provide the following arguments via environment variable: \\- {s}: The origin to serve the cluster admin panel at (ex: https://admin.example.com) \\- {s}: The username for the initial cluster operator \\- {s}: The password for the initial cluster operator , .{ admin_origin_envvar, admin_username_envvar, admin_password_envvar }, ); std.os.exit(1); }, else => return err, }; } } const ConnectionId = u64; var next_conn_id = std.atomic.Atomic(ConnectionId).init(0); fn thread_main(src: *api.ApiSource, srv: *http.Server) void { util.seedThreadPrng() catch unreachable; var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); srv.handleLoop(gpa.allocator(), .{ .api_source = src, .allocator = gpa.allocator() }, c.router); } pub fn main() !void { try util.seedThreadPrng(); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var cfg = try loadConfig(gpa.allocator()); var pool = try sql.ConnPool.init(cfg.db); try prepareDb(&pool, gpa.allocator()); var api_src = try api.ApiSource.init(&pool); var srv = http.Server.init(); defer srv.deinit(); const addr = "::1"; const port = 8080; try srv.listen(std.net.Address.parseIp(addr, port) catch unreachable); std.log.info("Listening on {s}:{}", .{ addr, port }); var i: usize = 0; while (i < cfg.worker_threads - 1) : (i += 1) { _ = try std.Thread.spawn(.{}, thread_main, .{ &api_src, &srv }); } thread_main(&api_src, &srv); }