wraith/README.md
Vantz Stockwell cb4b8ec136 docs: comprehensive README with architecture, build, and plugin guide
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 06:35:21 -04:00

243 lines
9.7 KiB
Markdown

<p align="center">
<img src="images/wraith-logo.png" alt="Wraith" width="128" />
</p>
<h1 align="center">Wraith</h1>
<p align="center">
Native desktop SSH + RDP + SFTP client — a MobaXTerm replacement built with Go and Vue.
</p>
<p align="center">
<!-- badges -->
<img alt="Go" src="https://img.shields.io/badge/Go-1.22+-00ADD8?logo=go&logoColor=white" />
<img alt="Wails v3" src="https://img.shields.io/badge/Wails-v3-red" />
<img alt="Vue 3" src="https://img.shields.io/badge/Vue-3-4FC08D?logo=vuedotjs&logoColor=white" />
<img alt="License" src="https://img.shields.io/badge/License-MIT-blue" />
</p>
---
## 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