Architecture
Bastion Terminal is a local-first desktop application built on Tauri 2, React 18, and a Rust core. This page describes the runtime architecture: how the layers talk, where state lives, and which subsystems are involved in a typical SSH session.
High-level diagram
UI (React · TypeScript)
│
│ typed Tauri IPC (commands + events)
▼
Rust core (src-tauri)
├── hosts / groups / snippets → SQLite (bastion.db)
├── private keys → OS keychain
├── SSH service → russh / ssh2
├── local PTY → portable-pty
├── updater → signed Tauri updater
└── settings / theme registry
│
▼
OS layer: keychain, OS updater, terminal PTY, OS notifications Frontend (React + Vite)
The UI is a single-page React app, organized around feature folders:
components/, stores/, hooks/, lib/, and styles/. State is managed with zustand; forms use react-hook-form + zod.
xterm.js renders the terminal grid, with addons for fit, web links, and the search overlay. Themes are CSS custom properties on :root[data-theme=…] and are applied to the xterm theme on switch.
Rust core
The Rust crate lives in src-tauri/ and exposes commands to the frontend over typed Tauri IPC. Major responsibilities:
- Hosts / groups / snippets: CRUD on top of SQLite via
sqlx. Migrations live insrc-tauri/migrations/. - Private keys: generated, imported, and stored in the OS keychain (Credential Manager / Keychain / Secret Service). The key material never crosses the IPC boundary as plaintext.
- SSH service: manages sessions, reads and stores known-host fingerprints, and performs password and private-key auth. SSH agent authentication is not enabled in this build.
- Local PTY: opens a shell on this machine using
portable-pty. Used by the local terminal alongside remote sessions. - Updater: Tauri updater settings in
tauri.conf.json, including a public key. Version 1.1.0 reads latest.json from downloads.bastion.hexifytech.com.
State and storage
| Data | Storage |
|---|---|
| Host metadata, groups, snippets, settings | Local SQLite (bastion.db) |
| Private keys | OS keychain |
| Passwords | Memory only (active connect) |
| Terminal scrollback | In-memory |
| Update checks | latest.json on downloads.bastion.hexifytech.com. |
IPC contract
The frontend never reads from disk directly. Every read and write goes through a typed Tauri command in src/lib/. Commands are namespaced by feature:
hosts.*: list, get, create, update, delete, reordergroups.*: same shape, scoped to groupssnippets.*: list, get, run, savessh.*: open session, auth, fingerprint, disconnectkeys.*: generate, import, list, deletesettings.*: get, update, theme, font, cursor, scrollback
Security boundaries
- The webview cannot reach the filesystem, network, or OS keychain directly. All access goes through Rust commands.
- Private keys are read from the keychain inside the SSH service and are never logged or returned to the frontend.
- Passwords live in memory for the active connect and are dropped on disconnect.
- Update artifacts are verified against the embedded public key before install.
Test pyramid
- Vitest: unit tests for stores, hooks, and lib helpers.
- Playwright: critical UI flows in the Vite web preview.
- Rust integration tests: SSH flows, fingerprint verification, keychain storage.
- Phase 7 QA matrix: manual and automated hardening before release.