Interactive TUI, SSH 2FA, multi-forward tunnels, and connection testing#37
Interactive TUI, SSH 2FA, multi-forward tunnels, and connection testing#37aretcamgoz wants to merge 57 commits into
Conversation
The daemon's per-tunnel cleanup goroutine unconditionally deleted a tunnel from d.tunnels once its run() exited, so a dropped 2FA (keyboard-interactive) tunnel vanished from `list` and showed as closed — the NeedsAuth status was unreachable in practice. Hold a tunnel.Desc snapshot of a dropped 2FA tunnel in a separate needsAuth map. The snapshot is taken in the cleanup goroutine after <-t.Closed (run() has exited, t.Status is stable) under d.mutex, so no live tunnel's status is read off-lock. listTunnels merges the map into its response; openTunnel clears the entry when re-opening; closeTunnel succeeds against a needsAuth-only entry by dropping it. Fix the TUI's selectedIsRunning so a NeedsAuth tunnel (now reported by list) counts as not-running: enter on it re-opens (re-auth) rather than closes.
Enforce the multi-forward validation rules in config.Load: - A tunnel must not set both the legacy local/remote shorthand and [[tunnels.forward]] blocks. Checked from the raw pre-normalization state in validateRawForwards, before normalizeForwards folds the shorthand away. - A tunnel must define at least one forward, via either form. Also checked in validateRawForwards. - Each forward's local/remote addresses are validated against its mode (local required for local, remote and socks modes; remote required for local, remote and socks-remote modes) in validateForwards, called from Validate. - Forward names, when set, must be unique within a tunnel. Update stale TUI test fixtures that built forward-less Desc values, which config.Load now correctly rejects on the post-save reload. The mode -> canonical-name mapping lives once as the exported tunnel.Mode.ConfigValue method; config validation and the TUI form call it instead of keeping their own copies.
The 'open' confirmation message read the legacy singular LocalAddress/RemoteAddress/Mode fields, which are unset for tunnels configured with [[tunnels.forward]] blocks. Migrate it to a describeForwards helper that renders every forward from Desc.Forwards. Add daemon unit tests for the Cmd/Resp JSON round-trip carrying a multi-forward Desc, and e2e coverage for list/close of a multi-forward tunnel.
boring list now renders each tunnel from Desc.Forwards instead of the legacy singular local/remote/mode fields. A single-forward tunnel still renders inline on one line; a multi-forward tunnel renders a connection-level header row plus one indented branch sub-row per forward, each showing the forward's label and local -> remote. The grouped-tree layout lives in a new internal/table/tunnels.go (TunnelTable) because the generic flat Table cannot express a header row followed by indented sub-rows with their own aligned columns. Forward.label is exported as Forward.Label so the CLI can reuse it.
|
Hi @aretcamgoz, thanks for your interest in the project and your contributions. My goal is to keep That said, I think there might be some interesting additions/fixes in your PR. Give me some time to experiment with it, and we can potentially formulate 1-2 issues for smaller-scoped changes based on that and work from there? |
|
Hi @aretcamgoz, I think two good first bug fixes that we can spin off into dedicated issues (and later PRs) would be:
More on the scope-expanding side, but potentially worth exploring would be:
Would you like to open corresponding issues? Thanks again. |
|
Lack of multiple port forwards per ssh tunnel is the one thing that is preventing me from using this program and suggesting it to others. It really needs to support something like: |
This is a sizeable, multi-feature contribution — four related additions plus a couple of fixes. I'm aware that's a lot for a single PR; I'm happy to split it into separate focused PRs, or to open issues to discuss scope first, if you'd prefer — just say the word.
Everything is backward compatible: existing configs and the existing CLI commands (
open/close/list/edit) behave exactly as before.Features
Interactive terminal UI —
boring tuiA Bubble Tea dashboard for managing tunnels and editing the config without hand-editing
.boring.toml:.boring.toml(the original is backed up once as.boring.toml.bak).SSH interactive authentication — 2FA and key passphrases
boring openprompts on the terminal; the TUI shows a modal.OpenIPC exchange (AuthPrompt/AuthReply).needs authstatus so the user can re-open it.Multiple forwards over one SSH connection
[[tunnels]]entry can carry multiple[[tunnels.forward]]blocks — one SSH connection serving many port forwards (one handshake, one authentication, one 2FA prompt). The legacy single-local/remoteform is unchanged: it is simply a tunnel with one forward, so existing configs need no edits.boring test(aliast)boring test <patterns>....Fixes
IdentityAgentssh_config directive, so agents configured that way (e.g. 1Password's SSH agent) are used — previously only$SSH_AUTH_SOCKwas consulted.identitykey before unconfigured agent keys, so a server'sMaxAuthTrieslimit no longer cuts off the right key when the agent holds many.Notes
charmbracelet/bubbletea,lipgloss,bubbles(TUI only).Test plan