╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │113 } }, err); │ │114 │ │115 if (@TypeOf(runnable) != void) { │ │118 - try runnable.run(); │ │116 + try runnable.run(wui_server.socket.address.getPort(), ssh_server.socket.address.getPort());│ │117 } else { │ │118 try tasks.await(io); │ │119 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │130 err: *std.Io.Writer, │ │131 runnable: anytype, │ │132 ) !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. │ │133 + // always fall back, so another instance viewing a second repo just binds │ │134 + // its own port │ │135 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 - }; │ │136 + var wui_server = try listenWithFallback(io, wui_listen_address, true); │ │137 defer wui_server.deinit(io); │ │138 │ │139 var tasks: std.Io.Group = .init; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │176 }, handle); │ │177 } │ │178 │ │179 +// bind the address. when it's taken, fall back to an OS-assigned port, or │ │180 +// fail without `fallback`. no reuse_address: on linux it also sets │ │181 +// SO_REUSEPORT, which would let this bind silently share a port an │ │182 +// already-running server holds rather than seeing it as taken. │ │183 +fn listenWithFallback(io: std.Io, listen_address: ListenAddress, fallback: bool) !std.Io.net.Server {│ │184 + const address = try std.Io.net.IpAddress.parseIp4(listen_address.host, listen_address.port); │ │185 + return address.listen(io, .{}) catch |listen_err| switch (listen_err) { │ │186 + error.AddressInUse => blk: { │ │187 + if (!fallback) return listen_err; │ │188 + const any_port = try std.Io.net.IpAddress.parseIp4(listen_address.host, 0); │ │189 + break :blk try any_port.listen(io, .{}); │ │190 + }, │ │191 + else => |e| return e, │ │192 + }; │ │193 +} │ │194 + │ │195 fn parseListenAddress(value: []const u8) !ListenAddress { │ │196 const colon = std.mem.lastIndexOfScalar(u8, value, ':') orelse return error.InvalidListenAddress;│ │197 if (colon == 0 or colon + 1 >= value.len) return error.InvalidListenAddress; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │570 break :blk try ui.Session.init(&session_arena, &repo, .{ .user_id = &admin_user_id });│ │571 }; │ │572 session.is_terminal = true; │ │573 - // try uses the default serve options below, so the footer's url points at │ │574 - // the default web UI port. │ │575 - session.web_port = try (srv.Options{}).wuiPort(); │ │573 │ │574 // start the server │ │575 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │614 io: std.Io, │ │615 key_path: []const u8, │ │616 │ │620 - pub fn run(self: @This()) !void { │ │617 + pub fn run(self: @This(), web_port: u16, ssh_port: u16) !void {│ │618 + _ = web_port; │ │619 std.debug.print( │ │620 \\ │ │621 \\connect to the TUI with: │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │624 - \\ ssh -p 8022 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null localhost│ │622 + \\ ssh -p {d} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null localhost │ │623 \\ │ │624 \\create a git repo and push over SSH: │ │625 \\ mkdir -p temp-try/client/test │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │628 \\ echo "hello" > hello.txt │ │629 \\ git add hello.txt │ │630 \\ git commit -m "let there be light" │ │633 - \\ GIT_SSH_COMMAND='ssh -p 8022 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -i {s}' git push localhost:admin/test HEAD:master│ │631 + \\ GIT_SSH_COMMAND='ssh -p {d} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -i {s}' git push localhost:admin/test HEAD:master │ │632 \\ │ │633 \\to quit, press enter. │ │634 \\ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │637 - , .{self.key_path}); │ │635 + , .{ ssh_port, ssh_port, self.key_path }); │ │636 // portable stdin read via std.Io — equivalent to the │ │637 // std.posix.read(STDIN_FILENO, ...) that doesn't compile│ │638 // on windows. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │655 session: *ui.Session, │ │656 repo: *Repo, │ │657 │ │660 - pub fn run(self: @This()) !void { │ │661 - // launch the TUI │ │658 + pub fn run(self: @This(), web_port: u16, ssh_port: u16) !void { │ │659 + _ = ssh_port; │ │660 + // launch the TUI; the footer points at whatever port was bound │ │661 + self.session.web_port = web_port; │ │662 try hx.ui.run(self.io, self.allocator, self.session, self.repo);│ │663 } │ │664 }; │ ╰─────────────────────────────────────────────────────────────────────────────────────╯