# sshdesk: a remote desktop that rides the SSH session you already have

> A Python remote desktop with no VNC listener, no web server and no extra port: OpenSSH launches it as a forced command and the graphical session arrives inside your terminal. Two dependencies, nine entry points, and a security warning the page puts above its own features list.

**rylena/sshdesk** — A full interactive remote desktop delivered entirely through SSH and displayed directly in your terminal.

- Repository: https://github.com/rylena/sshdesk
- Stars: 532 · Forks: 29
- Language: Python
- License: MIT
- Published: 2026-09-18 · Updated: 2026-09-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/rylena-sshdesk

## Anyone who authenticates gets the live desktop

The security notice sits above the feature list, which is the right place for it. Anyone who can authenticate to an SSHDESK account can see and control the active graphical session, and the page's instruction is to treat it like physical console access. It also tells you to keep a second administrative login available while configuring a forced command, which is the failure mode you would otherwise create for yourself: a forced command that cannot launch, with the only way in being the account whose access you just changed. There is no second password database, no separate application-level credential and no role split, so the SSH account is the entire security boundary. Treat every SSHDESK account as an interactive console account, and do not hand one to someone you would not hand a keyboard.

## The forced command is a per-user drop-in, validated before sshd restarts

The server side is a pair of scripts and a config file with your name in it. The manual route installs the server for a given user and display:

```bash
sudo ./scripts/install-server.sh \
  "$USER" "$DISPLAY" "${XAUTHORITY:-$HOME/.Xauthority}"

./scripts/configure-sshd.sh "$USER" |
  sudo tee "/etc/ssh/sshd_config.d/90-sshdesk-$USER.conf"
sudo sshd -t
```

The generated file lands in the sshd drop-in directory under a name that carries the user name, so two accounts on one machine do not overwrite each other, and the user's display and Xauthority are passed through explicitly rather than guessed. The last command is the important one: it asks sshd to validate its own configuration. A drop-in with a syntax error takes sshd down on restart, and on a remote machine that is an outage, so a validation step between writing the file and reloading the service is the difference between a five-second mistake and a locked-out host.

## One PTY carries pixels, keys, clicks and disconnects

The architecture claim in one line is that everything travels through the single SSH PTY: keyboard, mouse, resize events, changed pixels and session cleanup. OpenSSH authenticates the user and starts SSHDESK as a forced command, so the graphical session appears inside the terminal you already logged in with, and the three command forms on the page are the same login with different intent. A bare login starts the desktop, because desktop is the default; asking for shell gets a normal login shell; naming desktop explicitly selects it. There is no browser, no custom SSH client, no VNC or RDP listener, no web server and no additional network port, which is the feature that matters on a network where you cannot open one.

## Two renderers, picked by what your terminal can draw

Terminals are split into two classes and the page is explicit about which is which. Kitty, Ghostty and WezTerm receive sharp real-pixel tiles, delivered as palette-compressed PNG images through the Kitty graphics protocol, with tmux passthrough so the same works inside a multiplexer. Every ordinary ANSI terminal falls back to a lower-resolution colour-cell renderer, which is why OpenSSH, PuTTY, mobile clients and embedded SSH terminals stay usable rather than becoming useless. Below that sit three more fallbacks: true colour, then 256-colour, 16-colour, Unicode and ASCII. The active frame-rate targets differ by class too, 60 frames per second on the sharp path and 30 on the ANSI path, with an adaptive idle presentation when nothing is moving, and the scheduler is designed to drop stale frames rather than accumulate latency behind them.

## Windows capture is experimental because of Session 0

The important-notice block is unusually specific about what the installers cannot fix. Cross-platform installation does not remove operating system security boundaries. macOS still asks for Screen Recording and Accessibility permission, and no script can remove that prompt. Windows OpenSSH normally runs in Session 0, the isolated session with no visible desktop, so Windows forced-command capture remains experimental even though the one-line Windows installer itself is supported. Any operating system can be the SSH client; Linux is the recommended host. That last line is the deployment advice in practice, and it is reinforced by the repair path for Wayland: if an older install closes with a capture error or behaves like a slow screenshot slideshow, you log into the machine's own graphical desktop, open a local terminal and re-run the one-line command, which upgrades GNOME to the persistent PipeWire backend and preserves the existing login.

## The one-line installers execute downloaded code as administrator

Both single-command installers fetch and run a script from the main branch:

```bash
curl -fsSL https://raw.githubusercontent.com/rylena/sshdesk/main/scripts/install.sh | sh
```

with a PowerShell equivalent for Windows. The page does not hide what this means: a one-line installer executes downloaded code with administrator permission during setup, and it tells you to read the script first if that is not appropriate for the machine. What the installers do afterwards is more than a download: they detect the operating system, install missing Python and OpenSSH prerequisites, install SSHDESK, validate graphical access and the forced-command configuration, and start the platform's OpenSSH service. There is a `--user` option for when automatic user detection guesses wrong, and the unattended path downloads the script first and passes a Tailscale choice, with matching PowerShell switches. That Tailscale question is asked only after SSHDESK and OpenSSH setup has succeeded, so the tunnel is never on the critical path, and the wording is careful about scope: Tailscale carries normal OpenSSH over the private tailnet and does not replace OpenSSH or add a second authentication mode. On Wayland the installer varies by compositor too, giving GNOME one persistent Mutter and PipeWire stream with compositor-native input, while KDE Plasma and wlroots get a capture command plus a checksum-verified ydotool helper.

## A full desktop in two dependencies and nine commands

The packaging is unusually spare for something with this much going on. The runtime dependency list is Pillow at 9.0 or newer plus python-xlib on Linux and nothing else, and everything heavy is optional: NumPy and a headless OpenCV sit behind a fast extra and are described as X11 acceleration paths, and macOS capture needs the Quartz framework binding behind a macos extra. The ruff configuration sets a line length of 100 and targets Python 3.10, matching the declared minimum. Then there are nine console entry points for one tool: the main client, a local mode, a server mode, a benchmark, two agent entry points, the forced-command wrapper the sshd drop-in calls, a split layout and a remote mode. It also ships a uv lock file alongside a setuptools build, so the project carries two dependency-management stories at once.

## The Wayland table lists a capture library under the wrong name

The manual Linux section maps each session type to a capture tool and an input tool. X11 uses FFmpeg with XBC, or MIT-SHM, or Pillow with XCB, for capture, and XTest for input. wlroots compositors such as Sway and Hyprland use grim with ydotool. GNOME Wayland uses a persistent Mutter plus PipeWire or GStreamer and the Mutter RemoteDesktop API. KDE Plasma Wayland uses spectacle with ydotool. The X11 capture cell names XBC where the neighbouring cell spells XCB, which is a typo rather than a different library, and it is the kind that would cost someone an afternoon. The requirements below the table are more consequential: GNOME needs PyGObject, GStreamer base introspection and the PipeWire plugin, other Wayland desktops need ydotool 1.0.4 or newer, non-GNOME Wayland input needs ydotoold access to /dev/uinput, and the page says plainly not to run the whole server as root.

## Conclusion

sshdesk is for the case where opening another port is the thing you cannot do, and it earns that by refusing to add one. Read the warning before anything else: any account that can authenticate gets the live graphical session, which makes it console-equivalent rather than file-transfer-equivalent, and the configuration procedure is designed around that, with a per-user drop-in validated before sshd restarts. Two practical caveats. Windows capture stays experimental because of Session 0, so plan a Linux host. And the one-line installers execute downloaded code with administrator permission, which the page itself tells you to read before running.

## FAQ

### Is SSH the same as a remote desktop?

Not by itself, and sshdesk is an example of the difference. SSH gives you a session on a machine; a remote desktop also needs a way to see and control what is on its screen. sshdesk supplies that second half inside the SSH session, by starting itself as a forced command and drawing the graphical session into the terminal.

### Does sshdesk open any network ports?

No additional ones. OpenSSH authenticates the user and launches sshdesk as a forced command, and keyboard, mouse, resize events, changed pixels and session cleanup all travel through the single SSH PTY. There is no browser, custom SSH client, VNC or RDP listener, second password database or web server.

### Who can see my desktop when sshdesk is running?

Anyone who can authenticate to an SSHDESK account, which is why the page says to treat it like physical console access and to keep a second administrative login while configuring a forced command. There is no application-level credential and no role separation.

### Does sshdesk work on Windows and macOS hosts?

The installers support Linux, macOS and Windows 10/11, and any OS can be the client, but Linux is the recommended host. macOS still requires Screen Recording and Accessibility permission, and Windows OpenSSH normally runs in Session 0, so Windows forced-command capture remains experimental.

### What does sshdesk need to install?

Python 3.10 or newer, a working Python venv and an OpenSSH server, plus capture and input tools for the active display stack: XTest on X11, grim and ydotool on wlroots, the Mutter RemoteDesktop API on GNOME, and spectacle plus ydotool on KDE Plasma. The one-line installer handles these automatically.

### How does sshdesk draw the desktop without a graphics protocol?

It picks a renderer by terminal. Kitty, Ghostty and WezTerm get palette-compressed PNG tiles through the Kitty graphics protocol, including through tmux passthrough, at a 60 FPS target. Other ANSI terminals get a colour-cell renderer at 30 FPS, with true-colour, 256-colour, 16-colour, Unicode and ASCII fallbacks beneath it.

## Sources

- [Issues](https://github.com/rylena/sshdesk/issues)
- [License: MIT](https://github.com/rylena/sshdesk/blob/main/LICENSE)
- [README](https://github.com/rylena/sshdesk/blob/main/README.md)
- [rylena/sshdesk on GitHub](https://github.com/rylena/sshdesk)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rylena-sshdesk
