Skip to content

Desktop & mobile client

The UniSSH client is a cross-platform SSH client (macOS / iOS / Linux / Windows / Android) built on Tauri v2 with a React 19 + TypeScript frontend and a Rust backend that wraps the existing core (rust-core, crate unissh-ffi) directly as a path dependency.

The UI is dark-first and premium-technological — Hanken Grotesk + JetBrains Mono, three theme families (mono is the default; nebula and barbie are opt-in), five accent presets, light/dark/auto modes with an AA-verified twin each, eighteen terminal themes (five of them light) plus a custom theme editor, a desktop shell and a purpose-built mobile shell.

src/ React + TS frontend
theme/ design tokens, ThemeProvider, keyframes/fonts
components/ primitives (Icon + ~60 glyphs, Btn, Tag, AuthBadge, StatusDot, …)
shell/ Shell.tsx (title bar, sidebar ↔ icon rail, vault switcher, nav)
bridge/ types.ts (DTO mirrors) + api.ts (typed invoke wrappers per command)
store/ zustand: route/vault/data/terminals/tunnels/overlays, ctx, toast
views/ ViewHosts, ViewTerminal (real xterm.js), ViewRun (mounts ViewFleet
and ViewBroadcast as two modes of one screen), ViewSftp, ViewTunnels,
ViewKnown, ViewSecrets (SSH keys/agent + passwords/notes), ViewSettings
overlays/ Entry (onboarding/kit/unlock), Modals, CommandPalette, ImportPreview, …
mobile/ MobileApp.tsx (bottom tabs, push stack, sheets, FAB)
src-tauri/ Rust backend
src/lib.rs Tauri builder, plugins, ~160 command handlers, AppState
src/commands.rs every command wraps the blocking core call in spawn_blocking
src/dto.rs serde DTOs <-> unissh-ffi records/enums
src/observers.rs SessionObserver/Exec/Broadcast/SftpProgress -> tauri::ipc::Channel
src/state.rs registries the core does not keep (sessions/tunnels/sftp/…)
src/error.rs ApiError mirrors FfiError (keeps structured HostKeyMismatch)
Cargo.toml depends on unissh-ffi = { path = "../../rust-core/crates/ffi" }
  • The core’s Core facade is synchronous/blocking (it owns its own tokio runtime). Every Tauri command therefore runs the call on a blocking thread via spawn_blocking.
  • Terminal / SFTP / broadcast output streams back over tauri::ipc::Channel — the Rust observer forwards the bytes and the frontend feeds them straight into xterm.js.
  • The core hands out Arc<SshSession|SshTunnel|SftpFfi|…> and forgets them, so AppState owns their lifecycle, keyed by a generated id.

The design prototype showed some indicators the core cannot back; these were intentionally dropped or made real rather than faked:

  • Host “online” / ping / cipher labels are removed — a host shows as active only when it has a live terminal session in-app.
  • Clipboard auto-clear and biometric unlock are wired to real platform features (biometric is mobile-only).
  • A per-host Forward the agent toggle serves a deliberately narrow agent to the target: one key, a confirmation for every signature, add/remove/lock refused, and never to a jump host. Off by default — see Connecting to hosts.
  • Snippets and session recording (asciicast, exportable) are per host and documented in Connecting to hosts.
  • A per-host System agent auth kind delegates signing to the operating system’s ssh-agent. That is how hardware works: FIDO/U2F tokens, PKCS#11 smart cards, Secure Enclave keys, 1Password and gpg-agent all sign through the agent. For such a host the key lives outside the vault — see the threat model.
  • Node 20.19+ / 22.12+ and Rust 1.85+.
  • The sibling rust-core/ must be present (consumed as a path dependency).
  • Linux desktop: libwebkit2gtk-4.1-dev libgtk-3-dev libsoup-3.0-dev libjavascriptcoregtk-4.1-dev librsvg2-dev libssl-dev libxdo-dev libayatana-appindicator3-dev.
  • iOS: macOS + Xcode + CocoaPods. Android: Android Studio + SDK + NDK.

See Install & prerequisites.

Terminal window
npm install
npm run tauri dev # desktop dev (vite + the Rust app)
npm run build # typecheck + vite production build (frontend only)
npm run tauri build # desktop bundle (.app/.dmg/.deb/.AppImage/.msi)
# mobile (run init once):
npm run tauri ios init && npm run tauri ios dev
npm run tauri android init && npm run tauri android dev

Desktop bundles are produced by CI on every PR/push and attached to GitHub Releases on a version tag — unsigned by design (no developer identity is attached). See CI/CD & releases for the unsigned-install notes.