Wraith

Wraith

Native desktop SSH + RDP + SFTP client — a MobaXTerm replacement built with Go and Vue.

Go Wails v3 Vue 3 License

--- ## Features - **Multi-tabbed SSH terminal** with xterm.js + WebGL rendering - **SFTP sidebar** on every SSH session (MobaXTerm's killer feature) -- same SSH connection, separate channel - **RDP** via FreeRDP3 dynamic linking (purego, no CGO) - **Encrypted vault** -- master password derived with Argon2id, secrets sealed with AES-256-GCM - **Connection manager** with hierarchical groups, tags, color labels, and full-text search - **7 built-in terminal themes** -- Dracula, Nord, Monokai, One Dark, Solarized Dark, Gruvbox Dark, MobaXTerm Classic - **Tab detach / reattach** -- sessions live in the Go backend; tabs can be torn off into separate windows and reattached without dropping the connection - **MobaXTerm import** -- plugin interface for `.mobaconf` and other formats - **Command palette** (Ctrl+K) for quick connection search and actions - **Single binary** -- ships as `wraith.exe` + `freerdp3.dll`, no Docker, no database server ## Tech Stack ### Backend (Go) | Component | Technology | Purpose | |-----------|-----------|---------| | Framework | Wails v3 | Desktop shell, multi-window, type-safe Go-to-JS bindings | | SSH | `golang.org/x/crypto/ssh` | SSH client, PTY, key/password auth | | SFTP | `github.com/pkg/sftp` | Remote filesystem over SSH channel | | RDP | FreeRDP3 via `purego` | RDP protocol, bitmap rendering | | Database | SQLite via `modernc.org/sqlite` (pure Go) | Connections, credentials, settings, themes | | Encryption | AES-256-GCM + Argon2id | Vault encryption at rest | ### Frontend (Vue 3 in WebView2) | Component | Technology | Purpose | |-----------|-----------|---------| | Framework | Vue 3 (Composition API) | UI framework | | Terminal | xterm.js 5.x + WebGL addon | SSH terminal emulator | | CSS | Tailwind CSS 4 | Utility-first styling | | Components | Naive UI | Tree, tabs, modals, dialogs | | State | Pinia | Reactive stores for sessions, connections, app state | | Build | Vite 6 | Frontend build tooling | ## Prerequisites | Tool | Version | Install | |------|---------|---------| | Go | 1.22+ | [go.dev/dl](https://go.dev/dl/) | | Node.js | 20+ | [nodejs.org](https://nodejs.org/) | | Wails CLI | v3 | `go install github.com/wailsapp/wails/v3/cmd/wails3@latest` | ## Quick Start ```bash # Clone git clone https://github.com/vstockwell/wraith.git cd wraith # Install frontend dependencies cd frontend && npm install && cd .. # Run in dev mode (hot-reload frontend + Go backend) wails3 dev ``` The app opens a 1400x900 window. On first launch you will be prompted to create a master password for the vault. ## Building ```bash # Production build for Windows wails3 build # Output: build/bin/wraith.exe ``` The build embeds the compiled frontend (`frontend/dist`) into the Go binary via `//go:embed`. Ship `wraith.exe` alongside `freerdp3.dll` for RDP support. ## Project Structure ``` wraith/ main.go # Entry point -- Wails app setup, service registration go.mod # Go module (github.com/vstockwell/wraith) internal/ app/ app.go # WraithApp -- wires all services, vault create/unlock db/ sqlite.go # SQLite open with WAL mode, busy timeout, FK enforcement migrations.go # Embedded SQL migration runner migrations/ 001_initial.sql # Schema: groups, connections, credentials, ssh_keys, # themes, host_keys, connection_history, settings vault/ service.go # Argon2id key derivation + AES-256-GCM encrypt/decrypt connections/ service.go # Connection and Group CRUD (hierarchical tree) search.go # Full-text search + tag filtering via json_each() settings/ service.go # Key-value settings store (vault salt, preferences) theme/ builtins.go # 7 built-in theme definitions service.go # Theme CRUD + idempotent seeding session/ session.go # SessionInfo struct + state machine (connecting/connected/detached) manager.go # Concurrent session manager -- create, detach, reattach, 32-session cap plugin/ interfaces.go # ProtocolHandler + Importer + Session interfaces registry.go # Plugin registry -- register/lookup protocol handlers and importers frontend/ package.json # Vue 3, Pinia, Naive UI, Tailwind CSS, Vite vite.config.ts # Vite + Vue + Tailwind plugin config src/ main.ts # App bootstrap -- createApp, Pinia, mount App.vue # Root component layouts/ MainLayout.vue # Sidebar + tab bar + session area + status bar UnlockLayout.vue # Master password entry screen components/ sidebar/ ConnectionTree.vue # Hierarchical connection/group tree SidebarToggle.vue # Collapse/expand sidebar session/ TabBar.vue # Draggable session tabs SessionContainer.vue # Active session viewport common/ StatusBar.vue # Bottom status bar stores/ app.store.ts # Global app state (sidebar, vault status) connection.store.ts # Connection + group state session.store.ts # Active sessions state images/ wraith-logo.png # Application logo ``` ## Architecture ``` Go Backend Wails v3 Bindings Vue 3 Frontend (services + business logic) (type-safe Go <-> JS) (WebView2) | WraithApp ─────────────────────────┼──────────────> Pinia Stores |-- VaultService | |-- app.store |-- ConnectionService | |-- connection.store |-- ThemeService | |-- session.store |-- SettingsService | | |-- SessionManager | Vue Components |-- PluginRegistry | |-- MainLayout | |-- ConnectionTree SQLite (WAL mode) | |-- TabBar wraith.db | |-- SessionContainer %APPDATA%\Wraith\ | |-- StatusBar ``` **How it fits together:** 1. `main.go` creates a `WraithApp` and registers Go services as Wails bindings. 2. Wails generates type-safe JavaScript bindings so the Vue frontend can call Go methods directly. 3. The Vue frontend uses Pinia stores to manage reactive state, calling into Go services for all data operations. 4. All secrets (passwords, SSH keys) are encrypted with AES-256-GCM before being written to SQLite. The encryption key is derived from the master password using Argon2id and is never persisted. 5. Sessions are managed by the Go `SessionManager` -- they are decoupled from windows, enabling tab detach/reattach without dropping connections. **Data storage:** SQLite with WAL mode at `%APPDATA%\Wraith\wraith.db` (Windows) or `~/.local/share/wraith/wraith.db` (Linux/macOS dev). Foreign keys enforced, 5-second busy timeout. ## Plugin Development Wraith uses a plugin registry with two interfaces: `ProtocolHandler` for new connection protocols and `Importer` for loading connections from external tools. ### Implementing a ProtocolHandler ```go package myplugin import "github.com/vstockwell/wraith/internal/plugin" type MyProtocol struct{} func (p *MyProtocol) Name() string { return "myproto" } func (p *MyProtocol) Connect(config map[string]interface{}) (plugin.Session, error) { // Establish connection, return a Session } func (p *MyProtocol) Disconnect(sessionID string) error { // Clean up resources } ``` ### Implementing an Importer ```go package myplugin import "github.com/vstockwell/wraith/internal/plugin" type MyImporter struct{} func (i *MyImporter) Name() string { return "myformat" } func (i *MyImporter) FileExtensions() []string { return []string{".myconf"} } func (i *MyImporter) Parse(data []byte) (*plugin.ImportResult, error) { // Parse file bytes into ImportResult (groups, connections, host keys, theme) } ``` ### Registering Plugins Register handlers and importers with the plugin registry during app initialization: ```go app.Plugins.RegisterProtocol(&myplugin.MyProtocol{}) app.Plugins.RegisterImporter(&myplugin.MyImporter{}) ``` The `ImportResult` struct supports groups, connections, host keys, and an optional theme -- everything needed to migrate from another tool in a single import. ## Contributing 1. Fork the repository 2. Create a feature branch (`git checkout -b feat/my-feature`) 3. Make your changes 4. Run tests: `go test ./...` 5. Run frontend checks: `cd frontend && npm run build` 6. Commit and push your branch 7. Open a Pull Request ## License [MIT](LICENSE) -- Copyright (c) 2026 Vantz Stockwell