╭ serve.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const xit = @import("xit"); 3 const rp = xit.repo; 4 const web = @import("./web.zig"); 5 const serve_ssh_protocol = @import("./serve_ssh_protocol.zig"); 6 const serve_ssh = @import("./serve_ssh.zig"); 7 const serve_http = @import("./serve_http.zig"); 8 9 pub const Options = struct { 10 http_listen: []const u8 = "127.0.0.1:8080", 11 ssh_listen: []const u8 = "127.0.0.1:8022", 12 wui_listen: []const u8 = "127.0.0.1:8000", 13 data_dir: []const u8 = ".", 14 // test mode, used by the networking tests. it serves repos directly by 15 // their on-disk path instead of resolving them as <owner>/<repo> through 16 // the event store, giving the plain git-server behavior those tests rely on. 17 is_test: bool = false, 18 }; 19 20 const ListenAddress = struct { 21 host: []const u8, 22 port: u16, 23 }; 24 25 pub fn run( 26 comptime repo_kind: rp.RepoKind, 27 comptime any_repo_opts: rp.AnyRepoOpts(repo_kind), 28 io: std.Io, 29 allocator: std.mem.Allocator, 30 cwd_path: []const u8, 31 options: Options, 32 err: *std.Io.Writer, 33 runnable: anytype, 34 ) !void { 35 // create the data dir 36 37 const data_dir_path = try std.fs.path.resolve(allocator, &.{ cwd_path, options.data_dir }); 38 defer allocator.free(data_dir_path); 39 40 const data_dir = try std.Io.Dir.cwd().createDirPathOpen(io, data_dir_path, .{}); 41 defer data_dir.close(io); 42 43 // create the repos dir 44 45 const repo_root_path = try std.fs.path.resolve(allocator, &.{ data_dir_path, "repos" }); 46 defer allocator.free(repo_root_path); 47 48 try std.Io.Dir.cwd().createDirPath(io, repo_root_path); 49 50 // the admin repo path is where the user/repo metadata events live 51 52 const admin_repo_path = try std.fs.path.resolve(allocator, &.{ data_dir_path, "admin" }); 53 defer allocator.free(admin_repo_path); 54 55 // create http listener 56 57 const http_listen_address = try parseListenAddress(options.http_listen); 58 const http_address = try std.Io.net.IpAddress.parseIp4(http_listen_address.host, http_listen_address.port); 59 var http_server = try http_address.listen(io, .{ .reuse_address = true }); 60 defer http_server.deinit(io); 61 62 // create ssh listener 63 64 const ssh_listen_address = try parseListenAddress(options.ssh_listen); 65 const ssh_address = try std.Io.net.IpAddress.parseIp4(ssh_listen_address.host, ssh_listen_address.port); 66 var ssh_server = try ssh_address.listen(io, .{ .reuse_address = true }); 67 defer ssh_server.deinit(io); 68 69 // load or generate the SSH host key 70 const host_key = try serve_ssh_protocol.HostKey.loadOrGenerate(io, allocator, data_dir_path); 71 72 // disk-backed store for web login sessions 73 const session_store = try web.SessionStore.init(io, data_dir); 74 defer session_store.deinit(); 75 76 // create wui listener 77 78 const wui_listen_address = try parseListenAddress(options.wui_listen); 79 const wui_address = try std.Io.net.IpAddress.parseIp4(wui_listen_address.host, wui_listen_address.port); 80 var wui_server = try wui_address.listen(io, .{ .reuse_address = true }); 81 defer wui_server.deinit(io); 82 83 // start task group 84 85 var tasks: std.Io.Group = .init; 86 defer tasks.cancel(io); 87 88 // run listeners 89 90 try err.print("serving HTTP on {s}, repo root {s}\n", .{ options.http_listen, repo_root_path }); 91 try err.flush(); 92 93 serve_http.runListener(repo_kind, any_repo_opts, io, allocator, repo_root_path, admin_repo_path, options.is_test, &http_server, &tasks, err); 94 95 try err.print("serving SSH on {s}\n", .{options.ssh_listen}); 96 try err.flush(); 97 98 const ssh_session_handler = serve_ssh.SessionHandler{ 99 .admin_repo_path = admin_repo_path, 100 .repo_root_path = repo_root_path, 101 .is_test = options.is_test, 102 }; 103 serve_ssh.runListener(io, allocator, &host_key, &ssh_session_handler, &ssh_server, &tasks, err); 104 105 try err.print("serving web UI on http://{s}/\n", .{options.wui_listen}); 106 try err.flush(); 107 108 web.run(io, allocator, &wui_server, &tasks, admin_repo_path, session_store, err); 109 110 if (@TypeOf(runnable) != void) { 111 try runnable.run(); 112 } else { 113 try tasks.await(io); 114 } 115 } 116 117 fn parseListenAddress(value: []const u8) !ListenAddress { 118 const colon = std.mem.lastIndexOfScalar(u8, value, ':') orelse return error.InvalidListenAddress; 119 if (colon == 0 or colon + 1 >= value.len) return error.InvalidListenAddress; 120 const port = try std.fmt.parseInt(u16, value[colon + 1 ..], 10); 121 return .{ .host = value[0..colon], .port = port }; 122 } 123