DEV Community

Cover image for Why I Built an Open-Source, E2EE-Synced Termius Alternative (and What Broke Along the Way)
kobaltGIT
kobaltGIT

Posted on

Why I Built an Open-Source, E2EE-Synced Termius Alternative (and What Broke Along the Way)

Like many developers and DevOps engineers, my daily workflow lives inside an SSH terminal.

For years, many of us relied on modern tools like Termius. It was slick, cross-platform, and solved the hassle of juggling keys and server IPs. But then came the paywalls: server sync behind a monthly subscription, closed-source binaries, and your server metadata hosted on proprietary clouds. On the other hand, open-source alternatives like Tabby are great, but Electron eats RAM for breakfast, while classics like PuTTY or MobaXterm feel anchored in 2005.

I wanted something different:

  1. Truly cross-platform & lightweight (native desktop and mobile, without Electron bloat).
  2. Zero-Knowledge Self-Hosted Sync (my keys, my VPS, end-to-end encrypted).
  3. Production Safety Guardrails (visual warnings and confirmation modals before you run rm -rf / or DROP TABLE).
  4. Native AI Integration (MCP) so modern coding assistants (Cursor, Claude Desktop) can safely inspect servers without bypassing safety rules.

That’s how Shellit was born.

Here is an honest breakdown of how it's built, the architectural decisions behind it, and the real-world bugs we fought during development.


πŸ›  What is Shellit?

Shellit is a cross-platform SSH/SFTP client, server management hub, and terminal workspace built from scratch using Dart & Flutter.

1. Production Guard (PROD Guard)

We’ve all had that split-second cold sweat when executing a command in the wrong tab. Shellit tags environments as DEV, STAGE, or PROD.
When connected to a PROD node:

  • The terminal pane receives a distinctive pulsing red warning border.
  • A heuristic parser intercepts destructive patterns (rm -rf, DROP, reboot, fork bombs) and triggers an interactive confirmation modal before the keystroke reaches the PTY stream.

2. Self-Hosted Zero-Knowledge E2EE Cloud Sync

Instead of paying for cloud sync, Shellit comes with a standalone, ultra-lightweight sync relay server (servers/sync_server, consuming <20MB RAM, Docker-ready).

  • All server entries, SSH keys, snippets, and directory mappings are derived and encrypted on-device via Argon2id and AES-256-GCM.
  • The relay only handles opaque encrypted blobs. It works out-of-the-box over plain HTTP inside private mesh networks like Tailscale and WireGuard.

3. Model Context Protocol (MCP) Gateway

Instead of bolting on a generic AI chat, Shellit implements a native MCP Server (SSE 2024-11-05 spec). AI assistants like Claude Desktop, Cursor, or Windsurf can connect to Shellit as a tool provider to run commands and inspect logs β€” but every action remains subject to PROD Guard policies and audit logging.

4. Matrix Tiling & Broadcast Input

Workspaces support horizontal, vertical, and 2x2 grid splits with drag-and-drop tabs. With Broadcast Input, your keystrokes replicate across multiple active server consoles simultaneously.


πŸ› The Architecture: Contract-First Monorepo

To prevent the codebase from turning into spaghetti, we enforced strict package boundaries around a foundation core:

Shellit/
β”œβ”€β”€ apps/
β”‚   └── shellit/             # Root app, routing & Riverpod DI
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ core_foundation/     # Domain entities & Result<T>/Failure models
β”‚   β”œβ”€β”€ storage_vault/       # Drift + SQLCipher (AES-256) & Argon2id
β”‚   β”œβ”€β”€ ssh_network_core/    # dartssh2, PTY streams & asciinema recorder
β”‚   β”œβ”€β”€ terminal_ui/         # xterm.dart, tabs & matrix split engine
β”‚   └── desktop_plugin_sdk/  # Sandboxed WebView IPC plugin protocol
└── servers/
    └── sync_server/         # Headless sync relay (Pure Dart + SQLite)
Enter fullscreen mode Exit fullscreen mode

Sensitive keys are held in SQLCipher and zeroized in memory upon locking or closing.


πŸ› The War Stories: Real Engineering Under the Hood

Writing an SSH client is deceptively difficult. The moment you step outside the happy path of a basic terminal stream, platform quirks start biting.

Here are a few real bugs we documented in our Chronicle:

1. The "Silent Terminal" (Windows IME vs. Flutter Focus)

Early on, the terminal on Windows would randomly stop accepting keystrokes. The cursor was blinking, the SSH connection was alive, but typing did nothing.

  • The Cause: Windows Input Method Editor (IME) and Flutter’s focus tree were fighting for hardware key routing. The text input client lost raw key dispatch whenever focus transitioned between split panes.
  • The Fix: We decoupled terminal input from standard text widgets and built a dedicated HardwareInputRouter that binds directly to raw key events with an explicit ticker guard.

2. The "Slammed Door" Tab Destruction

In Flutter, switching tabs typically destroys the hidden widget tree to reclaim memory. For an SSH client, that meant switching tabs dropped your running top or tail -f session.

  • The Fix: We implemented an off-stage preservation model using IndexedStack coupled with a PaneReloadController.
  • The Complication: This triggered a known Flutter assertion failure (duplicate GlobalKey detected) inside the SFTP dual-pane tree. We had to isolate pane keys dynamically based on active viewport indices.

3. Fake Security vs. True Argon2id Re-Keying (BUG-018)

In the initial prototype, "Change Master Password" was essentially a UI stub that re-encrypted the UI state.

  • The Fix: We ripped that out and replaced it with a true zero-downtime database re-keying routine. It exports the decrypted SQLCipher vault in memory, derives a new 256-bit Argon2id key, writes the new cipher header, and zeroes out the previous memory buffer.

4. Cross-Device Key Sync on Android

During cross-platform testing, desktop-to-desktop sync worked flawlessly, but an Android device syncing with the self-hosted relay received the host list while SSH keys were missing or failing decryption.

  • The Cause: Android's background sync isolate lacked the native sodium/crypto bindings when invoked outside the main UI thread.
  • The Fix: We re-architected the cryptographic payload pipeline to ensure isolate-safe key derivation before writing to Android's secure keystore.

πŸ“Š Current Status & What's Next

Today, Shellit is covered by over 200+ unit and widget tests, with 0 Dart analyzer issues and 24 documented bug post-mortems.

Features ready in the repository:

  • Full SSH & Dual-pane SFTP (with visual chmod 0755 permissions editor)
  • Native ConPTY terminal for local shells (PowerShell, WSL, cmd)
  • Asciinema v2 (.cast) session recording & audit logs
  • Desktop Plugin SDK (.shellit packages, including a Docker Container Monitor)

Try It Out / Contribute

Shellit is 100% Free and Open Source under the GNU General Public License v3.0 (GPLv3).

If you’re managing servers, tired of Termius subscriptions, or just interested in high-performance Flutter desktop engineering, take it for a spin!

⭐ Stars, issues, and Pull Requests are warmly welcome. Let me know what you think in the comments!

Top comments (0)