docs: comprehensive README with architecture, build, and plugin guide
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d42f000f8f
commit
cb4b8ec136
242
README.md
Normal file
242
README.md
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
<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
|
||||||
Loading…
Reference in New Issue
Block a user