╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────╮ │1 const std = @import("std"); │ │2 const xit = @import("xit"); │ │3 const rp = xit.repo; │ │4 +const ui = @import("./ui.zig"); │ │5 const web = @import("./web.zig"); │ │6 const serve_common = @import("./serve_common.zig"); │ │7 const serve_ssh_protocol = @import("./serve_ssh_protocol.zig");│ ╰──────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │109 try err.print("serving web UI on http://{s}/\n", .{options.wui_listen}); │ │110 try err.flush(); │ │111 │ │111 - runWebListener(io, allocator, &wui_server, &tasks, admin_repo_path, session_store, err);│ │112 + runWebListener(io, allocator, &wui_server, &tasks, .{ .remote = .{ │ │113 + .admin_repo_path = admin_repo_path, │ │114 + .session_store = session_store, │ │115 + } }, err); │ │116 │ │117 if (@TypeOf(runnable) != void) { │ │118 try runnable.run(); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │121 } │ │122 } │ │123 │ │124 +// serve just the web UI for a single local repo, running `runnable` (the │ │125 +// local TUI) in the foreground while the listener runs in the background. │ │126 +// when the default port is taken, a random ephemeral port is used instead; │ │127 +// the bound port is passed to `runnable.run`. │ │128 +pub fn runLocal( │ │129 + io: std.Io, │ │130 + allocator: std.mem.Allocator, │ │131 + local: ui.RepoSource, │ │132 + err: *std.Io.Writer, │ │133 + runnable: anytype, │ │134 +) !void { │ │135 + // no reuse_address here: on linux it also sets SO_REUSEPORT, which would │ │136 + // let this bind silently share a port an already-running server holds │ │137 + // instead of failing over to a random one. │ │138 + const wui_listen_address = try parseListenAddress((Options{}).wui_listen); │ │139 + const wui_address = try std.Io.net.IpAddress.parseIp4(wui_listen_address.host, wui_listen_address.port);│ │140 + var wui_server = wui_address.listen(io, .{}) catch |listen_err| switch (listen_err) { │ │141 + // the default port is taken; bind port 0 so the OS assigns a free one │ │142 + error.AddressInUse => blk: { │ │143 + const any_port = try std.Io.net.IpAddress.parseIp4(wui_listen_address.host, 0); │ │144 + break :blk try any_port.listen(io, .{}); │ │145 + }, │ │146 + else => |e| return e, │ │147 + }; │ │148 + defer wui_server.deinit(io); │ │149 + │ │150 + var tasks: std.Io.Group = .init; │ │151 + defer tasks.cancel(io); │ │152 + │ │153 + runWebListener(io, allocator, &wui_server, &tasks, .{ .local = local }, err); │ │154 + │ │155 + try runnable.run(wui_server.socket.address.getPort()); │ │156 +} │ │157 + │ │158 fn runWebListener( │ │159 io: std.Io, │ │160 allocator: std.mem.Allocator, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────╮ │161 net_server: *std.Io.net.Server, │ │162 tasks: *std.Io.Group, │ │125 - admin_repo_path: []const u8, │ │126 - session_store: web.SessionStore,│ │163 + host: web.Host, │ │164 err: *std.Io.Writer, │ │165 ) void { │ │166 const Context = struct { │ ╰─────────────────────────────────────────╯ ╭─────────────────────────────────────────────╮ │167 io: std.Io, │ │168 allocator: std.mem.Allocator, │ │132 - admin_repo_path: []const u8, │ │133 - session_store: web.SessionStore,│ │169 + host: web.Host, │ │170 err: *std.Io.Writer, │ │171 }; │ │172 │ ╰─────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │173 const handle = struct { │ │174 fn h(ctx: Context, stream: std.Io.net.Stream) void { │ │175 defer stream.close(ctx.io); │ │140 - web.handleConnection(ctx.io, ctx.allocator, stream, ctx.admin_repo_path, ctx.session_store, ctx.err) catch |request_err| {│ │176 + web.handleConnection(ctx.io, ctx.allocator, stream, ctx.host, ctx.err) catch |request_err| { │ │177 serve_common.logError(ctx.err, "web ui request failed: {s}\n", .{@errorName(request_err)}); │ │178 }; │ │179 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │182 serve_common.runListener(io, net_server, tasks, err, "web ui", Context{│ │183 .io = io, │ │184 .allocator = allocator, │ │149 - .admin_repo_path = admin_repo_path, │ │150 - .session_store = session_store, │ │185 + .host = host, │ │186 .err = err, │ │187 }, handle); │ │188 } │ ╰────────────────────────────────────────────────────────────────────────────────╯