DEV Community

angga prabuwisesa
angga prabuwisesa

Posted on

Built a SSH client that uses 1/10th RAM of Electron apps (Rust + Tauri v2)

I built Termiaxial - a SSH/SFTP client that replaces Termius, PuTTY, and WinSCP with a single, fast, and secure application.

Why?

I was frustrated with existing SSH clients:

  • Electron apps (Termius, Royal TSX) eat 200MB+ RAM
  • Slow startup times (5s+)
  • Heavy CPU usage even when idle
  • Proprietary software (can't audit code)
  • Expensive licensing ($100+/year)

The Solution: Rust + Tauri v2

Tauri v2 uses the OS's native webview instead of bundling Chromium. Combined with Rust backend, this achieves:

  • 50MB idle RAM (vs 200MB+ for Electron apps)
  • <1.5s startup (vs 5s+ for competitors)
  • Native performance with React + TypeScript frontend
  • Fully open source (MIT license)

Tech Stack

Framework: Tauri v2
Frontend:  React 18 + TypeScript + Tailwind CSS
Backend:   Rust (russh, tokio)
Terminal:  Xterm.js v5
Crypto:    ring (AES-GCM-256) + argon2
Database:  SQLite
Enter fullscreen mode Exit fullscreen mode

Features

Core

  • ✅ SSH authentication (password + private key: RSA, ED25519)
  • ✅ Full terminal emulator (Xterm.js, 256 colors, 5000-line scrollback)
  • ✅ SFTP file explorer with drag-drop upload/download
  • ✅ Multi-tab sessions with auto-reconnect

Security

  • ✅ Master Password with Argon2id hashing
  • ✅ AES-GCM-256 encrypted credential vault
  • ✅ Local SQLite storage (no cloud by default)

AI & Productivity

  • ✅ AI Assistant (OpenAI, Ollama, Anthropic)
  • ✅ Terminal analysis with Ctrl+Space shortcut

Performance Benchmarks

Metric Termiaxial Termius PuTTY
Idle RAM 50MB 200MB+ 30MB
Startup Time 1.5s 5s+ 0.3s
CPU Usage <1% 3-5% <1%
Bundle Size 8MB 120MB+ 1MB

Current Status

Repository is 85% ready for open source launch:

  • ✅ 7 GitHub Actions workflows (CI/CD, Security, Release)
  • ✅ Multi-platform builds (macOS Intel/ARM, Linux AMD64/ARM64)
  • ✅ Comprehensive documentation
  • ✅ Professional templates for issues/PRs

Roadmap

  • v1.5: SSH Tunneling + Snippet Manager
  • v2.0: Session Recording + Cloud Sync
  • v2.5: Team Collaboration + Enterprise features

GitHub

github.com/angga30/termiaxial

⭐ Star if you like the project
🍴 Fork if you want to contribute
🐛 Report issues on GitHub

For Rust Developers

This project showcases:

  • Tauri v2 for cross-platform desktop apps
  • Rust + React integration
  • AES-GCM-256 encryption with ring
  • SSH implementation with russh
  • SQLite with rusqlite
  • AI integration with OpenAI/Ollama/Anthropic

Would love to get feedback from the Rust community! Especially interested in:

  • Performance optimizations
  • Security improvements
  • Feature suggestions
  • Architecture feedback

Built with ❤️ by developers, for developers.

Top comments (5)

Collapse
 
hiyoyok profile image
hiyoyo

Great project! I'm also building macOS apps with Tauri v2 + Rust and the RAM difference vs Electron is genuinely impressive in practice. One thing I ran into — have you tested the russh connection stability on long-running sessions (24h+)? Curious how you handle reconnect edge cases internally.

Collapse
 
angga_prabuwisesa_0d016cd profile image
angga prabuwisesa

Thanks! Great question — fellow Tauri+Rust dev knows exactly what to ask 😄

Keep-alive: We run a 30s SSH keep-alive loop via russh's built-in keepalive_interval config (SSH_MSG_IGNORE packets). If the server doesn't respond after 3 missed pings (keepalive_max: 3), the session is marked dead.

Detection: Our background tokio task per session uses select! across three channels — data from server, commands from frontend, and a 30s keep-alive tick that checks handle.is_closed(). The moment any signal indicates the connection dropped, we emit a SessionEvent::Disconnected through our event bus so the UI reacts immediately.

Edge cases we handle:

  • Network sleep/wake (laptop lid close) — detected on next keep-alive tick
  • Server-initiated disconnect (EOF/Close) — caught instantly in the channel loop
  • Zombie sessions — if the Rust Handle is dropped or closed, cleanup removes it from our DashMap atomically (we use Arc::ptr_eq to avoid race conditions with re-connections)

What we don't do yet (honest answer): Auto-reconnect with session resume isn't shipped yet. When a long-running session drops, we notify the user and they reconnect manually. SSH protocol doesn't natively support session resume (unlike Mosh), so true transparent reconnect would require a proxy layer or Mosh-style approach — that's on our roadmap but not trivial to get right.

For 24h+ sessions in practice, the keep-alive mechanism has been solid. The main failure mode we've seen is aggressive NAT timeouts on some corporate networks (<60s), which our 30s interval handles well.

Would love to hear what edge cases you've hit with russh — always looking to harden this further! 🚀

Collapse
 
hiyoyok profile image
hiyoyo

Thanks for the thorough breakdown — the three-channel select! approach is really clean, and using Arc::ptr_eq to avoid race conditions on reconnect is exactly the kind of detail that's easy to miss until it bites you.
The corporate NAT timeout point is interesting too. 30s feels like a solid sweet spot — aggressive enough to stay alive, but not noisy.
I'm also on Tauri v2 + Rust + tokio for my macOS tools, and I've run into similar reconnect edge cases with persistent ADB over Wi-Fi connections — the "connection looks alive but isn't" problem is surprisingly tricky to detect reliably.
Looking forward to the auto-reconnect feature — and agreed, the Mosh-style proxy approach sounds like the right way to do it properly. Not trivial, but worth it for long-running sessions!

Thread Thread
 
angga_prabuwisesa_0d016cd profile image
angga prabuwisesa

Thanks! Yeah, Arc::ptr_eq saved me from quite a few headaches when dealing with rapid reconnect states.

Regarding that ADB over Wi-Fi issue you mentioned—man, 'zombie sockets' are the worst. It’s exactly why TCP keepalives alone often fail us in corporate environments or flaky Wi-Fi, and why an application-level heartbeat becomes mandatory.

For the upcoming auto-reconnect feature, the plan is to robustly handle that specific 'looks alive but dead' state before moving into the full Mosh-style proxy architecture.

Appreciate the insights, and it’s awesome to connect with a fellow Tauri v2 + Rust developer on macOS. Will keep you posted on the progress!

Thread Thread
 
hiyoyok profile image
hiyoyo

"Zombie sockets" is exactly the right term for it — the connection reports as open but any actual I/O just silently fails. Application-level heartbeat is the only reliable way to catch that, agreed.
With ADB over Wi-Fi I ended up having to treat every "connected" state as unverified until a real command round-trips successfully. Sounds like you're hitting the same fundamental problem, just over SSH.
The approach of hardening the "looks alive but dead" detection before tackling full session resume makes a lot of sense — getting that state machine right is probably 80% of the work anyway.
Looking forward to seeing how the auto-reconnect lands. And likewise — always good to find another Tauri v2 + Rust dev in the wild!