A unified, cross-platform remote connection manager built in Rust with
egui.
e-sh (the e stands for egui) is a desktop application for managing and
launching remote sessions over SSH, SFTP, RDP, and VNC from one consistent
interface. It ships as two binaries: the main e-sh UI and a companion e-sh-rdp
helper that handles the RDP protocol.
Managing remote machines often requires juggling several different clients: an SSH
terminal, an SFTP file browser, and a remote desktop viewer. e-sh brings these
workflows together into a single native application that is fast to launch, easy
to script, and consistent across Linux, macOS, and Windows.
The goal is one UI, every protocol you need every day (SSH, SFTP, RDP, VNC) —
without sacrificing the keyboard-first, low-overhead feel that power users expect.
RDP support is handled by a lightweight companion binary (e-sh-rdp) that ships
alongside the main application.
-
Native, GPU-accelerated UI powered by
egui+egui_dock -
Multi-pane Layout: connection tree on the left, dockable tabbed terminals, status bar
-
Connection manager with add / edit / delete and right-click menu, grouped by tag
-
Inline
+button on the sidebar to create a new connection -
Polished layout edit dialog (header / grouped sidebar / scrollable detail / footer)
-
Toast notifications (info / success / warn / error) for connect, disconnect, save, delete, host-key, persistence, and session-end events
-
Persistent TOML store for connections (per-OS config dir) with transparent backward-compatible migration
-
SSH interactive shell:
russh0.60 transport,tokioasync runtimealacritty_terminal0.26 emulator rendered through a custom egui widget- Password, public-key, and SSH agent authentication (uses
$SSH_AUTH_SOCK, tries each loaded identity in order until one is accepted) ~expansion + native file picker (rfd) for selecting private keys- Encrypted-key passphrases
- 10 000-line scrollback with mouse-wheel scroll, click-and-drag selection, Cmd-C / Ctrl-Shift-C to copy, Cmd-V / Ctrl-Shift-V to paste, Shift-PageUp/PageDown and Shift-Home/End for keyboard scroll navigation
-
TOFU host-key verification:
- SHA-256 fingerprints stored in
host_keys.toml - Interactive modal prompt on first connect and on key mismatch
- Three actions: Reject, Accept once, Accept and save
- SHA-256 fingerprints stored in
-
SSH tunnels (configurable per connection):
- Local (
-L) — forward a local port to a remote host:port through the session - Remote (
-R) — bind a port on the remote server and forward back to a local target - Dynamic (
-D) — local SOCKS5 proxy (no-auth, CONNECT) tunneled over the session - Per-session collapsible Tunnels status strip in each tab showing live state per tunnel
(
pending/listening/failed/disabled), bound port when the OS picked one, and full error on hover for failed tunnels
- Local (
-
Chained jump hosts (ProxyJump):
- Per-connection ordered hop list (max depth 8), reorderable in the edit dialog
- True SSH-in-SSH via nested handshakes over
direct-tcpipchannels - Live "Resolved path" preview in the edit dialog
-
SFTP dual-pane file browser:
russh-sftp2.1.1 backend, opens its own SSH session per tab (independent of any shell tab)- Side-by-side Local and Remote panes with editable path bar and clickable breadcrumb path
- Right-click everything: per-row menu (Open / Upload-or-Download / Rename / Delete) and empty-pane menu (New folder / Refresh / Up)
- Multi-select with Finder/Explorer semantics: plain click = single, Cmd/Ctrl-click = toggle, Shift-click = range from anchor
- Bulk Upload / Download / Delete across the current selection (
N itemslabel when >1); Rename stays single-only - Drag-and-drop files or folders from the OS into either pane to upload / move into the current dir
- Recursive folder transfers (upload and download), with one aggregated transfer row per top-level command
- Per-transfer Cancel button (cooperative, partial files left on disk); progress bar + bytes/total + status line
- Filter field (🔍) per pane: case-insensitive substring match, with one-click clear
- Resizable columns (Name / Size / Modified) and click-to-sort column headers (toggle ▲ / ▼); directories grouped first regardless of sort key
- Folder / file / symlink icons; size auto-formatted (B / K / M / G / T)
-
RDP remote desktop viewer:
- Dual-backend architecture via the
e-sh-rdphelper binary:- IronRDP (built-in, Rust-native) — embedded framebuffer rendered as an egui texture with mouse, keyboard, and scroll wheel forwarding; works with Windows RDP and xrdp
- FreeRDP (external) — spawns
sdl-freerdp/xfreerdpas a separate window; required for servers that mandate the Graphics Pipeline (e.g. GNOME Remote Desktop / grd)
- Auto backend mode (default): tries IronRDP first, detects GFX-only disconnects, and automatically retries with FreeRDP — no manual configuration needed for most servers
- Per-connection configurable: backend selection (Auto / IronRDP / FreeRDP), resolution (custom width × height), and FreeRDP resize mode (dynamic resolution / smart sizing / static)
- NLA (CredSSP/NTLM) authentication, TLS, and automatic certificate acceptance
- xrdp compatibility: handles the stray
ServerDeactivateAllPDU that xrdp sends beforeServerDemandActive - Dirty-region bitmap transfer — only changed screen rectangles are sent from the helper to the UI, not the full framebuffer
- Advanced display settings in the connection edit dialog with platform-specific FreeRDP install instructions (macOS / Linux / Windows)
- Dual-backend architecture via the
-
VNC remote desktop viewer:
- Pure-Rust RFB (Remote Framebuffer) client — no external binaries or C libraries required
- Supports RFB protocol versions 3.3, 3.7, and 3.8
- VNC Authentication (DES challenge-response) and None security types
- Embedded framebuffer rendered as an egui texture with mouse, keyboard, and scroll wheel forwarding
- Handles Raw and CopyRect encodings
- Full keyboard input including typed characters (
!,@, uppercase letters, etc.) via X11 keysym mapping - Scroll wheel support via RFB button 4/5 emulation
- Periodic incremental framebuffer update requests to keep the display responsive during idle periods
- Clean shutdown — closing a tab or the app terminates the VNC session promptly without requiring force-quit
-
Credential storage:
- Passwords and key passphrases are encrypted with a master password you set
on first launch and stored in
secrets.enc.tomlnext to your other config - Encryption is
agepassphrase mode (scrypt KDF + ChaCha20-Poly1305); the master password never touches disk and is held in memory only for the lifetime of the running app connections.tomlis sanitized on every save: secrets never touch disk in plaintext- Legacy plaintext
connections.tomlfiles are migrated transparently into the encrypted store on first unlock
- Passwords and key passphrases are encrypted with a master password you set
on first launch and stored in
-
Command palette + keyboard shortcuts:
- Press Cmd-K (macOS) / Ctrl-K (Linux / Windows) — or Cmd/Ctrl-Shift-P — to open a fuzzy command palette
- Fuzzy search powered by
nucleo-matcher(the same algorithm family as Helix / Zed) - Commands: New connection, Open, Open SFTP, Edit (per saved connection), Switch to tab (per open tab), Close tab, Toggle sidebar, Lock secrets, Open recordings, Quit
- Global app shortcuts: Cmd/Ctrl-B toggle sidebar · Cmd/Ctrl-W close active tab · Cmd/Ctrl-Q quit
-
Automatic update checker:
- On launch, queries the GitHub Releases API for the latest version of
e-sh - Non-blocking background check — never delays startup or interferes with usage
- Toast notification when a newer version is detected (e.g. "v1.2.0 → v1.3.0")
- Persistent top-of-window banner with a one-click "Open release page" button
- Failures are silently logged — offline or rate-limited users are unaffected
- On launch, queries the GitHub Releases API for the latest version of
-
Session recording (opt-in, per connection):
- Enable on a per-connection basis via Advanced → Recording in the edit dialog (SSH and SFTP only)
- SSH sessions are recorded as gzipped asciicast v2 (
<uuid>.cast.gz) — replay withasciinema play <(gunzip -c file.cast.gz) - SFTP sessions are recorded as gzipped JSON Lines audit logs (
<uuid>.sftp.jsonl.gz) with one event per operation (list,upload,download,mkdir,rmdir,remove,rename,realpath,upload_cancelled,download_cancelled) — inspect withgunzip -c file.sftp.jsonl.gz | jq -c . - Server output only — your typed input (including passwords typed at prompts) is never captured; however recordings are stored plaintext on disk, so treat them like logs
- A built-in Recordings tab (open from the sidebar bottom link or the command palette) lists every recording with status (Complete / Incomplete / Partial / File missing), size, duration, and shortcuts to Reveal in Finder/Explorer, Copy path, Delete, or Clean up missing
- Manifests are stored at
recordings/recordings.tomlunder your config dir; recordings never auto-expire — clean up manually
- Plugin / scripting hooks
| Protocol | Purpose | Status |
|---|---|---|
| SSH | Interactive remote shell | Working (password + pubkey + agent, TOFU, tunnels -L/-R/-D, chained ProxyJump, scrollback + copy/paste, encrypted secret store, opt-in asciicast v2 recording) |
| SFTP | Secure file transfer / browse | Working (dual-pane browser, drag-drop, recursive transfers, multi-select, filter, sortable/resizable columns, cancel, opt-in JSONL audit recording) |
| RDP | Remote desktop viewer | Working (dual-backend: built-in IronRDP for Windows/xrdp with embedded viewer, auto-fallback to FreeRDP for GNOME Remote Desktop; NLA auth, dirty-region updates) |
| VNC | Remote desktop viewer | Working (pure-Rust RFB client, VNC auth + None, Raw + CopyRect encodings, embedded viewer with full mouse/keyboard forwarding) |
e-sh follows a layered architecture: a thin egui UI sits on top of a session
and connection manager, which dispatches to per-protocol adapters that wrap the
underlying transport libraries. Configuration and credentials are isolated in a
dedicated store so the UI and protocol layers never deal with secrets directly.
src/
├── app.rs EshApp: top-level eframe::App, wires everything together
├── updater.rs Background update checker (GitHub Releases API, semver compare)
├── core/
│ └── connection.rs Connection / AuthMethod / RdpBackend / ConnectionStore
├── config/
│ ├── store.rs ConfigPaths + connections.toml load/save
│ └── host_keys.rs TOFU HostKeyStore (host_keys.toml)
├── proto/
│ ├── ssh.rs russh client, host-key verifier, PTY session
│ ├── sftp.rs russh-sftp client, recursive transfers, cancel registry
│ ├── rdp.rs RDP session manager — spawns e-sh-rdp helper, binary protocol bridge
│ └── vnc.rs Pure-Rust VNC (RFB) client — auth, framebuffer updates, input forwarding
└── ui/
├── connection_tree.rs Left-side tree + "+" button
├── dock.rs egui_dock tab area + per-tab tunnels status strip
├── edit_dialog.rs Add / edit connection modal (auth, jump chain, tunnels, RDP display)
├── host_key_prompt.rs TOFU prompt modal
├── rdp_tab.rs RDP viewer tab — framebuffer texture, mouse/keyboard/scroll forwarding
├── vnc_tab.rs VNC viewer tab — framebuffer texture, mouse/keyboard/scroll forwarding
├── sftp_tab.rs Dual-pane SFTP browser (filter, sort, resize, multi-select)
├── status_bar.rs Bottom status bar
├── toast.rs Toast notification overlay
└── terminal_widget/ alacritty_terminal renderer
e-sh-rdp/ Standalone helper binary (separate crate)
└── src/main.rs IronRDP session + FreeRDP fallback, binary protocol over stdin/stdout
The high-level system diagram lives at:
Open it with diagrams.net (formerly draw.io) or the VS Code "Draw.io Integration" extension.
- Language: Rust (edition 2024, toolchain
1.85+) - UI:
egui0.34,eframe0.34,egui_dock0.19,egui_extras0.34 - Async runtime:
tokio1 - SSH:
russh0.60 - SFTP:
russh-sftp2.1 - Terminal emulator:
alacritty_terminal0.26 - RDP:
IronRDP(built-in, Rust-native) + FreeRDP (external fallback for GFX pipeline servers) - VNC: Pure-Rust RFB client with
des0.8for VNC authentication - File picker:
rfd0.15 - Encryption:
age0.11(scrypt + ChaCha20-Poly1305) for the credential store - Config: TOML via
serde+toml - Paths:
directories6 - Update checker:
reqwest(rustls) +semver+ GitHub Releases API - Logging:
tracing+tracing-subscriber
- Rust toolchain 1.85+ (edition 2024 support)
- A C toolchain (for some transitive dependencies)
- Platform-specific GUI dependencies for
egui/eframe(see the eframe docs)
git clone https://github.com/nexetry/e-sh.git
cd e-sh
cargo build --release
# Build the RDP helper (separate crate)
cargo build --release --manifest-path e-sh-rdp/Cargo.tomlcargo run --releaseThe compiled binaries will be at target/release/e-sh and
e-sh-rdp/target/release/e-sh-rdp. The main binary locates the helper
automatically when both are in the same directory.
Native, host-only release scripts that produce versioned archives in dist/:
# macOS (universal arm64+x86_64) or Linux x86_64
./scripts/build-release.sh
# Windows x86_64 (PowerShell)
pwsh scripts/build-release.ps1Output:
- macOS:
dist/e-sh-<version>-macos-universal.tar.gz— universal arm64+x86_64e-sh.appbundle, ad-hoc signed (Gatekeeper will warn on first launch — see First launch on macOS)dist/e-sh-<version>-macos-universal.dmg— drag-to-Applications installer (requirescreate-dmg; falls back to plainhdiutilif unavailable)
- Linux:
dist/e-sh-<version>-linux-x86_64.tar.gz— rawe-shbinary + READMEdist/e-sh_<version>-1_amd64.deb— Debian / Ubuntu package (requirescargo-deb; installs to/usr/bin/e-shwith a.desktopentry)
- Windows:
dist/e-sh-<version>-windows-x86_64.zip— portable archivedist/e-sh-<version>-x86_64.msi— MSI installer (requirescargo-wix+ WiX Toolset v3; runcargo wix initonce to generatewix/main.wxs)
Each archive and installer ships with a matching .sha256 checksum file.
Install the installer tooling (one-time, per host) with:
# macOS
brew install create-dmg
# Linux
cargo install cargo-deb
# Windows (PowerShell)
cargo install cargo-wixFor automated cross-platform release builds on git tag push (vX.Y.Z), the
GitHub Actions workflow at .github/workflows/release.yml builds all three
targets on their native runners (macos-latest, ubuntu-latest,
windows-latest) and attaches the artifacts to a GitHub Release.
e-sh is ad-hoc signed, not signed with a paid Apple Developer ID, so on
first launch macOS Gatekeeper will show:
"Apple could not verify 'e-sh' is free of malware that may harm your Mac or compromise your privacy."
This is expected for any unsigned / ad-hoc-signed app. To bypass:
Easiest — Finder
- Open Finder, locate
e-sh.app(Applications,dist/, wherever you put it). - Right-click (or Ctrl-click) the app → Open.
- Click Open in the warning dialog.
macOS remembers this choice and won't prompt again for that copy.
If right-click "Open" is blocked (macOS 14.4+ / Sequoia)
xattr -dr com.apple.quarantine /Applications/e-sh.appAdjust the path if you installed it elsewhere. Then double-click normally.
If macOS still complains
Open System Settings → Privacy & Security, scroll down — there will be a message like "e-sh was blocked" with an Open Anyway button. Click it once.
The Gatekeeper warning will reappear every time you install a new build (each new download / install gets a fresh quarantine flag). Until proper Developer ID signing + notarization is wired up, repeat the bypass per install.
- Launch
e-sh. - Click the
+next to Connections in the sidebar to create a new entry. - Fill in name, group, host, port, username, and choose an auth method:
- Password — type the password directly (encrypted with your master password)
- Public key — type the path or click
...to browse; supply a passphrase if the key is encrypted (passphrase is encrypted with your master password) - SSH agent — uses your running ssh-agent (
$SSH_AUTH_SOCK); each loaded identity is tried in order. Runssh-addto load keys.
- Save. Double-click the connection (or right-click → Open) to connect.
- On first connect to a host you'll be prompted to verify its key fingerprint (TOFU). Choose Accept and save to remember it.
- Right-click any connection to Open, Edit, Open SFTP, or Delete it.
- Open SFTP for any connection: right-click → Open SFTP (uses the connection's existing auth + jump chain over a fresh SSH session).
- Two panes: Local (your machine) and Remote (the server). Each pane has:
- A clickable breadcrumb path and an editable path text field (Enter to navigate).
- A 🔍 filter input — case-insensitive substring match against entry names.
- Resizable Name / Size / Modified columns; click any column header to sort, click again to flip direction (▲ / ▼). Folders are always grouped first.
- Selection: click = single, Cmd/Ctrl-click = toggle, Shift-click = range.
- Right-click an entry for: Open, Upload / Download (across panes), Rename (single-only), Delete. Right-click on empty space for: New folder, Refresh, Up.
- Drag and drop files or folders from your OS into either pane to upload / move into the current directory. Folder transfers recurse automatically.
- The bottom Transfers strip shows progress, bytes, and a per-transfer Cancel button. Use Clear finished to prune completed rows.
Configuration is stored as TOML under the standard OS config directory:
- Linux:
~/.config/com.nexetry.e-sh/ - macOS:
~/Library/Application Support/com.nexetry.e-sh/ - Windows:
%APPDATA%\nexetry\e-sh\
Files in that directory:
connections.toml— saved connections (sanitized; never contains plaintext secrets)secrets.enc.toml—age-encrypted password / passphrase store, unlocked at startup with your master passwordhost_keys.toml— TOFU host-key store (algorithm + SHA-256 fingerprint + first-seen timestamp)recordings/— session recordings directory (created on first opt-in recording):recordings.toml— manifest index of all recordings (id, connection, kind, started/ended timestamps, size, status)<uuid>.cast.gz— gzipped asciicast v2 files for SSH sessions<uuid>.sftp.jsonl.gz— gzipped JSON Lines audit logs for SFTP sessions
The first time you save a connection that needs a secret,
e-shasks you to set a master password. On every subsequent launch it asks you to enter that same password to unlock the encrypted store. The master password is held in memory only for the lifetime of the running app and is never written to disk. If you forget it, the encrypted secrets cannot be recovered — you will need to deletesecrets.enc.tomland re-enter your credentials.
e-sh is in early development (alpha). The SSH, SFTP, RDP, and VNC features are functional
end-to-end (connect, authenticate, render shell, browse + transfer files, view remote
desktops, persist host keys), but expect breaking changes to config formats and APIs.
- Application skeleton with
eframe/egui - Connection model + persistent storage (TOML)
- Add / edit / delete connections from the UI
- SSH adapter (interactive PTY) —
russh+alacritty_terminal - Password + public-key authentication
- TOFU host-key verification with persistence
- Native file picker for private keys
- Toast / inline error reporting
- SSH tunnels (
-L,-R,-DSOCKS5) - Chained jump host / bastion (ProxyJump, ordered N-hop)
- Per-session tunnel status display (collapsible)
- edit dialog
- SSH agent authentication
- Credential storage via
age-encrypted secret store - Terminal scrollback UI + selection / copy / paste
- SFTP adapter (dual-pane browser, drag-drop, recursive transfers, multi-select, filter, sortable/resizable columns)
- Command palette + keyboard-first navigation
- Native installers (
.dmg/.deb/.msi) in the release pipeline - Tabbed multi-session UI polish (split panes, drag-to-reorder)
- Connections tab UI polish (drag-to-reorder, grey, ellipsised and small text sub title)
- Session recording / logging (opt-in)
- RDP remote desktop viewer (dual-backend: IronRDP embedded + FreeRDP external fallback)
- VNC remote desktop viewer (pure-Rust RFB client, embedded viewer)
- Automatic update checker (GitHub Releases API, non-blocking)
- Plugin / scripting hooks
Q: Why another remote connection manager?
Most existing tools are either single-protocol, web-based, or shipped as heavy
Electron apps. e-sh aims for a fast, native application that covers the
common protocols with a consistent UX.
Q: Why egui instead of GTK / Qt / Tauri?
egui gives us a single Rust dependency, zero web runtime, and a UI that feels
the same on every OS.
Q: Does it support agent forwarding / jump hosts / X11 forwarding? Chained jump hosts (ProxyJump) are supported today with up to 8 hops per connection. SSH agent forwarding and X11 forwarding are not yet implemented.
Q: How does the RDP dual-backend work?
e-sh ships a helper binary (e-sh-rdp) that handles the RDP protocol. In
Auto mode (the default) it first tries the built-in IronRDP client, which
renders the remote desktop directly inside the egui tab. If the server requires
the Graphics Pipeline extension (e.g. GNOME Remote Desktop), IronRDP cannot
connect — the helper detects this and automatically retries with FreeRDP, which
opens a separate native window. You can also force a specific backend per
connection in Advanced → Display. FreeRDP must be installed separately:
brew install freerdp (macOS), apt install freerdp3-x11 (Linux), or
winget install --id FreeRDP.FreeRDP (Windows).
Q: Is it production ready? No. See Project Status.
Q: How do I replay a recorded session?
SSH recordings are asciicast v2
gzipped, so any asciicast-compatible player works. With
asciinema installed:
asciinema play <(gunzip -c ~/Library/Application\ Support/com.nexetry.e-sh/recordings/<uuid>.cast.gz)SFTP recordings are gzipped JSON Lines audit logs — one event per line. Inspect them with:
gunzip -c ~/Library/Application\ Support/com.nexetry.e-sh/recordings/<uuid>.sftp.jsonl.gz | jq -c .The built-in Recordings tab (sidebar bottom link, or Cmd/Ctrl-K →
Open recordings) lists every recording with status and shortcuts to Reveal,
Copy path, and Delete.
Q: Are my passwords captured in recordings?
No. e-sh only records server output (what you see on screen), not the
keys you type — so passwords, passphrases, and typed secrets never enter the
recording. That said, recordings are stored plaintext on disk (gzipped
only), so treat the recordings/ directory like any other log directory:
anything the server printed (file contents you cat-ed, tokens the server
echoed, etc.) will be in there. Recording is opt-in per connection and
off by default.
Q: macOS warns "Apple could not verify 'e-sh' is free of malware" — is the app unsafe?
No. e-sh is ad-hoc signed (not signed with a paid Apple Developer ID),
so Gatekeeper cannot verify the publisher and shows this warning on first
launch for any download. The binary itself is the same one you (or GitHub
Actions) built from this source repo. To bypass, see
First launch on macOS — short version: right-click
→ Open, or xattr -dr com.apple.quarantine /Applications/e-sh.app.
The warning will reappear after each fresh install until proper Developer ID
signing + notarization is added to the release pipeline (planned).
Q: macOS shows an AutoFill (e-sh) process in Activity Monitor — is it accessing my Keychain?
No. e-sh does not link Security.framework and does not call any Keychain
API (credentials are stored in secrets.enc.toml encrypted with your master
password via age). The AutoFill (<AppName>) process is
com.apple.SafariPlatformSupport.Helper.xpc, a system XPC helper that macOS
14+ AppKit preloads for any Cocoa application with a text-input responder
chain. You will see identical AutoFill (…) helpers listed for virtually
every native app on your system (Brave, WhatsApp, Raycast, etc.). The helper
is parented to launchd (not to e-sh), consumes only a few MB idle, and is
managed entirely by the OS — it is a cosmetic artifact of AppKit, not an
access by e-sh, and is outside application-developer control.
Contributions, bug reports, and feature requests are welcome. Until the architecture stabilizes, please open an issue to discuss substantial changes before sending a pull request.
- Fork the repo and create a feature branch.
- Run
cargo fmtandcargo clippy --all-targetsbefore committing. - Open a PR describing the change and the motivation.
This project is licensed under the MIT License.



