Files
vpsmgr/docs/web-ssh.md
T
bluebluesoda 5e28fc2983 feat(web-ssh): make the terminal survive a bad connection, and gate it
A browser reaching this panel is often a long way away, so the session is now
built to survive the link rather than be torn down by it.

Liveness first, because the old numbers were wrong: pings every 20s with a 60s
deadline. The previous setting was a single 15-minute timer, and since the
browser answers pings automatically it never fired for an idle user — the one
thing it did do was take fifteen minutes to notice a peer that had vanished.

- the shell outlives the socket. When the client goes away the PTY keeps running
  for 120s with its output landing in a 256 KiB ring (with a notice if anything
  is dropped). A client that comes back with its token resumes the same shell, so
  a tunnel, a cell handover or a sleeping laptop no longer costs a running build.
  The token travels in the first frame rather than the URL, is bound to the user,
  and lives in sessionStorage — reloading resumes, closing the window lets go.
- the page has its own heartbeat: a browser cannot see protocol-level pings, so
  the server also sends a small JSON one and the page reconnects with exponential
  backoff and jitter after 45s of silence, up to eight attempts.
- a handshake that neither completes nor fails would otherwise stall the retry
  loop for good, so an attempt that has not opened in 15s is abandoned.
- at the per-user cap the client is offered its slots back instead of being
  refused, which is the only way to reclaim a window the user can no longer see.
  Where there is room, the count is just a notice.

Three bugs the browser testing turned up, all of them real:

- Pty writes had no deadline, so a wedged Incus connection blocked the handler
  forever — resume hung after any pause long enough to break that socket.
- a chunk whose write failed was neither delivered nor buffered.
- a session stayed resumable between Close and the end of its drain.

An operator can now turn the whole thing off with `vps config set panel.web_ssh
false`: the button is not rendered and the page, the script and the connection
all refuse. It defaults on, including for a config written before the setting
existed.
2026-09-17 12:13:02 +00:00

2.8 KiB

Web SSH

A terminal in the browser: a shell inside the user's own container, opened from the Machine card (next to Start / Stop / Restart) in the user panel.

It is not an SSH client. The container runs on the panel host, so the panel talks to it through the Incus exec API over the daemon's unix socket, with a PTY. Nothing listens on the container side, there are no keys to hand out, and there is no extra network path to arrange.

Shape of it

  • Its own window. The button opens a separate browser window rather than a dialog, so it can be dragged to any size, moved to another screen or maximised. It remembers the size it was last dragged to and reopens that way.
  • Root shell. The shell runs as root inside the account's own container, which is the same access the user already has over SSH. The container is always taken from the session: a client cannot name a container it does not own.
  • One account, two shells. A third connection is offered the option to close the others instead of being refused, which is also how a slot held by a closed window is reclaimed.

Surviving a bad connection

The panel is often reached from far away, so the browser side is built to survive the link rather than to be torn down by it.

  • Liveness. The server pings every 20 seconds and treats 60 seconds of silence as gone. The browser answers pings from its network stack, so this works in a backgrounded tab too, and a connection that disappears without saying so is noticed in about a minute.
  • Reconnect. The page hears a small heartbeat the server sends alongside the protocol pings; hearing nothing for 45 seconds means the socket is dead even though it has not said so. It then reconnects with exponential backoff and jitter, up to eight attempts, after which a manual Reconnect is offered.
  • Resume. The shell outlives the socket: when the client goes away the PTY keeps running for 120 seconds and its output goes into a bounded buffer (256 KiB, with a notice if anything is dropped). A client that comes back with its session token resumes the same shell — a running build, an open editor and the shell's variables are all still there.
  • Redraw. A resumed terminal starts blank and is rebuilt from the buffered output, so a full-screen program is asked to repaint itself; if one does not, Ctrl+L forces it.
  • Token. The resume token lives in sessionStorage, so reloading the page comes back to the shell and closing the window lets it go.

Turning it off

vps config set panel.web_ssh false

The panel restarts and the feature is off: the button is not rendered, and the terminal page, its script and the terminal connection all refuse. true (the default) turns it back on. A configuration written before this feature existed comes out enabled.