Add HostKeyDialog modal with two modes: new host (informational with blue accent) and changed key (warning with red accent). Shows hostname, key type, and fingerprint in monospace. ConnectionTree now has @dblclick handler that calls sessionStore.connect(). Session store gains a connect() method that looks up the connection, checks for existing sessions, and creates a mock session tab. Pre-loaded mock sessions removed — sessions start empty and are created on double-click. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .gitea/workflows | ||
| docs | ||
| frontend | ||
| images | ||
| internal | ||
| .gitignore | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| main.go | ||
| README.md | ||
Wraith
Native desktop SSH + RDP + SFTP client — a MobaXTerm replacement built with Go and Vue.
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
.mobaconfand 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 |
| Node.js | 20+ | nodejs.org |
| Wails CLI | v3 | go install github.com/wailsapp/wails/v3/cmd/wails3@latest |
Quick Start
# 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
# 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:
main.gocreates aWraithAppand registers Go services as Wails bindings.- Wails generates type-safe JavaScript bindings so the Vue frontend can call Go methods directly.
- The Vue frontend uses Pinia stores to manage reactive state, calling into Go services for all data operations.
- 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.
- 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
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
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:
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
- Fork the repository
- Create a feature branch (
git checkout -b feat/my-feature) - Make your changes
- Run tests:
go test ./... - Run frontend checks:
cd frontend && npm run build - Commit and push your branch
- Open a Pull Request
License
MIT -- Copyright (c) 2026 Vantz Stockwell
