feat(ssh): watch a remote host with nothing installed on it - #66
Merged
Merged
Conversation
The probe loops on the far end and the TUI reads the records it emits, so a session costs one SSH connection rather than one per tick. No framing needed inventing: the probe already terminates each record with #END. Killing signals the PID directly, since the installed-portview path shells out to `portview kill` — precisely what is missing here. A remote session that ends now reports why on stderr after the terminal is restored. As a status line it rendered for one frame and then left with the alternate screen.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The last item in Tier 2.
portview ssh <host> watch --agentlessnow works, which means agentless mode covers every command.One connection, not one per tick
The obvious implementation re-runs the probe each second. That pays an SSH handshake per refresh and re-authenticates every second, which is both slow and conspicuous in auth logs. Instead the probe loops on the far end and this side reads what it sends back.
No framing had to be invented for that — the probe already ends each record with
#END, so the reader accumulates until it sees one. Theexit 0in the probe's no-collector branch ends the loop too, which is the right behaviour: nothing will change by trying again.Verified rather than assumed, by counting invocations of a stubbed
ssh:Rendering the first record proves nothing about refresh, so the check is that uptime advances and the connection count stays at one.
Kill needed its own path
The remote kill ran
run_oneshot(["kill", <port>]), which invokesportviewon the remote — exactly what agentless mode does not have. It would have failed at the moment of use, on the one action with consequences. Agentless sessions now signal the PID the probe reported:The PID is a
u32parsed from the probe's own output, so it cannot carry shell syntax.A failure the user could not see
Testing the no-collector path showed the error was invisible: setting a status message and quitting draws one frame, then the alternate screen is torn down and takes the message with it. The user got a blank exit with no reason.
Stream failures are now reported on stderr after the terminal is restored:
Same wording as the one-shot scan gives for the same host. This also fixes the pre-existing "Connection lost" case, which had the same problem.
Structure
RemoteFeeddistinguishes the two framings — a remote portview's one-array-per-line versus the probe's#END-terminated records. Reading is the only part of the TUI that needs to know which, soread_remote_snapshotis the only thing that branches; everything downstream seesVec<PortInfo>.Also
README claimed
watchrequires portview remotely, and that macOS/BSD hosts still do too — the latter was already stale from #63. Both corrected.181 tests, including feed-framing coverage (two records back to back,
--all, the no-collector message, and the JSON feed unchanged). fmt/clippy clean, macOS + Windows type-check.