╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ serve.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
83 // disk-backed store for web login sessions 84 const session_store = try web.SessionStore.init(io, data_dir); 85 defer session_store.deinit(); 86 87 // create wui listener 88 89 const wui_listen_address = try parseListenAddress(options.wui_listen); 90 var wui_server = try listen(io, wui_listen_address, port_policy); 91 defer wui_server.deinit(io); 92 93 // create ssh listener and its session state 94 95 const git_http_port: ?u16 = if (http_maybe) |*http| http.port() else null; 96 var ssh_maybe: ?SshService = if (options.ssh_listen) |value| blk: { 97 const address = try parseListenAddress(value); 98 var listener = BoundListener{ .address = address, .server = try listen(io, address, port_policy) }; 99 errdefer listener.deinit(io); 100 const ssh_port = listener.port(); 101 break :blk .{ 102 .listener = listener, 103 .host_key = try serve_ssh_protocol.HostKey.loadOrGenerate(io, allocator, data_dir_path), 104 .session_handler = .{ 105 .admin_repo_path = admin_repo_path, 106 .repo_root_path = repo_root_path, 107 .wui_port = wui_server.socket.address.getPort(), 108 .git_http_port = git_http_port, 109 .git_ssh_port = ssh_port, 110 .git_ssh_prefix = options.git_ssh_prefix, 111 .err = err, 112 }, 113 }; 114 } else null; 115 defer if (ssh_maybe) |*ssh| ssh.listener.deinit(io); 116 117 // start task group 118 119 var tasks: std.Io.Group = .init; 120 defer tasks.cancel(io); 121 122 // run listeners, printing the ports actually bound 123 124 const git_ssh_port: ?u16 = if (ssh_maybe) |*ssh| ssh.listener.port() else null; 125 126 if (http_maybe) |*http| { 127 try err.print("serving HTTP on {s}:{d}, repo root {s}\n", .{ http.address.host, http.port(), repo_root_path }); 128 try err.flush(); 129 serve_http.runListener(repo_kind, any_repo_opts, io, allocator, repo_root_path, admin_repo_path, &http.server, &tasks, err); 130 } 131 132 if (ssh_maybe) |*ssh| { 133 try err.print("serving SSH on {s}:{d}\n", .{ ssh.listener.address.host, ssh.listener.port() }); 134 try err.flush(); 135 serve_ssh.runListener(io, allocator, &ssh.host_key, &ssh.session_handler, &ssh.listener.server, &tasks, err); 136 } 137 138 try err.print("serving web UI on http://{s}:{d}/\n", .{ wui_listen_address.host, wui_server.socket.address.getPort() }); 139 try err.flush(); 140 141 runWebListener(io, allocator, &wui_server, &tasks, .{ .remote = .{ 142 .admin_repo_path = admin_repo_path, 143 .session_store = session_store, 144 .git_http_port = git_http_port, 145 .git_ssh_port = git_ssh_port, 146 .git_ssh_prefix = options.git_ssh_prefix, 147 } }, err); 148 149 if (@TypeOf(runnable) != void) { 150 try runnable.run(wui_server.socket.address.getPort(), git_http_port, git_ssh_port); 151 } else { 152 try tasks.await(io); 153 } 154 } 155 156 // serve just the web UI for a single local repo, running `runnable` (the 157 // local TUI) in the foreground while the listener runs in the background. 158 // when the default port is taken, a random ephemeral port is used instead; 159 // the bound port is passed to `runnable.run`. 160 pub fn runLocal( 161 io: std.Io, 162 allocator: std.mem.Allocator, 163 local: ui.RepoSource, 164 err: *std.Io.Writer, 165 runnable: anytype, 166 ) !void { 167 // always fall back, so another instance viewing a second repo just binds 168 // its own port 169 const wui_listen_address = try parseListenAddress((Options{}).wui_listen); 170 var wui_server = try listen(io, wui_listen_address, .fallback); 171 defer wui_server.deinit(io); 172 173 var tasks: std.Io.Group = .init; 174 defer tasks.cancel(io); 175 176 runWebListener(io, allocator, &wui_server, &tasks, .{ .local = local }, err); 177 178 try runnable.run(wui_server.socket.address.getPort()); 179 } 180 181 fn runWebListener( 182 io: std.Io, 183 allocator: std.mem.Allocator, 184 net_server: *std.Io.net.Server, 185 tasks: *std.Io.Group, 186 host: web.Host, 187 err: *std.Io.Writer, 188 ) void { 189 const Context = struct { 190 io: std.Io, 191 allocator: std.mem.Allocator, 192 host: web.Host, 193 err: *std.Io.Writer, 194 }; 195 196 const handle = struct { 197 fn h(ctx: Context, stream: std.Io.net.Stream) void { 198 defer stream.close(ctx.io); 199 web.handleConnection(ctx.io, ctx.allocator, stream, ctx.host, ctx.err) catch |request_err| { 200 serve_common.logError(ctx.io, ctx.err, "web ui request failed: {s}\n", .{@errorName(request_err)}); 201 }; 202 } 203 }.h; 204 205 serve_common.runListener(io, net_server, tasks, err, "web ui", Context{ 206 .io = io, 207 .allocator = allocator, 208 .host = host, 209 .err = err, 210 }, handle); 211 } 212 213 // what a listener does with a port that is already taken 214 const PortPolicy = enum { 215 exact, 216 // leave it to whoever holds it and take an OS-assigned port instead 217 fallback, 218 }; 219 220 fn listen(io: std.Io, listen_address: ListenAddress, policy: PortPolicy) !std.Io.net.Server { 221 const address = try std.Io.net.IpAddress.parseIp4(listen_address.host, listen_address.port); 222 return switch (policy) { 223 .exact => try address.listen(io, .{}), 224 .fallback => address.listen(io, .{}) catch |listen_err| switch (listen_err) { 225 error.AddressInUse => blk: { 226 const any_port = try std.Io.net.IpAddress.parseIp4(listen_address.host, 0); 227 break :blk try any_port.listen(io, .{}); 228 }, 229 else => |e| return e, 230 }, 231 }; 232 } 233 234 fn parseListenAddress(value: []const u8) !ListenAddress { 235 const colon = std.mem.lastIndexOfScalar(u8, value, ':') orelse return error.InvalidListenAddress; 236 if (colon == 0 or colon + 1 >= value.len) return error.InvalidListenAddress; 237 const port = try std.fmt.parseInt(u16, value[colon + 1 ..], 10); 238 return .{ .host = value[0..colon], .port = port }; 239 } 240