23 KiB
Hostkeeper: SSH/SFTP Management Tool - Design Document
Date: 2024-06-22 Status: Design Approved Version: 1.0-MVP
Executive Summary
Hostkeeper is a cross-platform SSH/SFTP management tool written in Go, inspired by Termius but implemented as a CLI tool with progressive TUI enhancement. The goal is to provide secure SSH credential management with cross-device sync capabilities through export/import functionality.
Key Design Decisions
- Architecture: TUI-first with tab-based session management (embedded terminal replacement)
- Interface: TUI for full experience (multiple SSH sessions, host management, SFTP); CLI for quick tasks and automation
- Storage: JSON/YAML files (Phase 1), encrypted storage (Phase 2)
- Platform: Cross-platform (Linux, macOS, Windows, Termux/Android)
- Sync: Manual export/import (Phase 1), cloud sync (future)
Project Requirements
Must-Have Features (MVP)
-
SSH Connection Management
- Username/password authentication
- SSH key authentication
- Host organization and search
- Connection testing
-
SFTP File Transfer
- Native SFTP client (Phase 1)
- Custom TUI dual-pane browser (Phase 2)
-
Credential Management
- Secure local storage
- Host metadata (tags, descriptions)
- Quick access patterns
-
SSH Key Management
- Generate SSH keys
- Import existing keys
- Export keys for distribution
- Key-Host association
-
Connection Snippets
- Save common commands
- Variable substitution
- Per-host snippets
-
Export/Import Functionality
- Cross-device credential transfer
- Backup and restore
- Merge capabilities
-
Cross-Platform Support
- Single binary distribution
- Termux compatibility
- Platform-specific optimizations
-
Security
- File permission management
- Secure credential storage
- Memory protection for sensitive data
Architecture Overview
High-Level Architecture
┌─────────────────────────────────────────────────────────────┐
│ HOSTKEEPER CLI │
│ (Single Binary) │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
┌───────▼──────────┐ ┌──────▼──────┐ ┌─────────▼────────┐
│ Command Layer │ │ TUI Layer │ │ Core Engine │
│ │ │ │ │ │
│ • connect │ │ • Host List │ │ • SSH Client │
│ • list │ │ • SFTP UI │ │ • SFTP Client │
│ • sftp │ │ • Key Mgmt │ │ • Key Manager │
│ • export │ │ • Snippets │ │ • Encryption │
│ • import │ │ • Settings │ │ • Config I/O │
│ • add │ │ │ │ │
└─────────────────┘ └─────────────┘ └──────────────────┘
│ │ │
└─────────────────────┼─────────────────────┘
│
┌─────────▼─────────┐
│ Storage Layer │
│ │
│ • hosts.json │
│ • keys.json │
│ • snippets.json │
│ • config.yaml │
└──────────────────┘
Key Design Principles
- Single Source of Truth - All data stored in JSON files
- TUI-First - TUI is the primary interface; CLI commands serve quick tasks and scripting
- Layered Architecture - UI → Business Logic → Storage
- Cross-Platform - Pure Go, no OS-specific dependencies
- Encrypt-Ready - Structure prepared for encryption upgrade
- Orange Theme - Distinct orange color palette to visually separate Hostkeeper from the native terminal, signaling the user is inside the application environment
Component Details
1. Command Layer (CLI Interface)
Framework: Cobra (standard Go CLI framework)
Major Commands:
hostkeeper connect <host> # SSH connection
hostkeeper list # List all hosts
hostkeeper sftp <host> # SFTP browser
hostkeeper add # Add new host
hostkeeper edit <host> # Edit host config
hostkeeper delete <host> # Delete host
hostkeeper export <file> # Export credentials
hostkeeper import <file> # Import credentials
hostkeeper key generate # Generate SSH key
hostkeeper key import <file> # Import existing key
hostkeeper snippet add <name> # Add command snippet
hostkeeper completion # Shell completion setup
Error Handling:
- Invalid command → Show help message
- Missing arguments → Prompt interactive input
- Connection errors → User-friendly error messages with hints
2. TUI Layer (Interactive Interface)
Framework: Bubble Tea (event-driven TUI) + Lipgloss (styling)
Theme: Orange/light color palette (#FF6B00, #FF9F43, #FFB800) — visually distinct from native terminal to signal the user is inside Hostkeeper
Tab System Architecture
┌─ Hosts ──── Server Nico ── SFTP ── Keys ── Settings ────────┐
│ │
│ [content of active tab] │
│ │
├───────────────────────────────────────────────────────────────┤
│ Ctrl+Q:close Ctrl+Tab:next Shift+Tab:prev ↑↓:nav /:search│
└───────────────────────────────────────────────────────────────┘
Tab Types:
- HostsTab — Host list with search, filter, sort, keyboard navigation
- SessionTab — Live interactive SSH session (input/output piped via Go SSH client)
- SFTPTab — Remote file browser with upload/download operations
- KeysTab — SSH key management (list, generate, import)
- SnippetsTab — Command snippets with variable substitution
- SettingsTab — Application configuration
SessionTab Internals:
- Connect via Go SSH client with stored credentials (password auto-injected)
- 2 goroutines per session: stdin → SSH channel, SSH stdout → TUI render
- Terminal resize event →
WindowChangevia SSH channel Ctrl+Qdisconnects session and closes tab- Multiple session tabs can be open simultaneously (e.g. connect to 3 servers at once)
Keyboard Navigation:
| Key | Action |
|---|---|
Ctrl+Tab / Shift+Tab |
Cycle tabs forward/backward |
Ctrl+N |
New tab (add host / new connection) |
Ctrl+Q |
Close current tab / disconnect session |
↑ / ↓ |
Navigate lists (hosts, keys, files) |
Enter |
Select host / open directory / confirm |
/ |
Search/filter within active tab |
q |
Quit Hostkeeper (confirmation dialog) |
3. Core Engine (Business Logic)
SSH Client Module
type SSHClient struct {
Hostname string
Port int
AuthMethod AuthMethod // password, key, or both
ClientConfig *ssh.ClientConfig
}
func (c *SSHClient) Connect() (*ssh.Client, error)
func (c *SSHClient) Execute(cmd string) (string, error)
func (c *SSHClient) Close() error
SFTP Client Module
type SFTPClient struct {
SSHClient *SSHClient
Client *sftp.Client
}
func (s *SFTPClient) Connect() error
func (s *SFTPClient) Upload(local, remote string) error
func (s *SFTPClient) Download(remote, local string) error
func (s *SFTPClient) List(path string) ([]os.FileInfo, error)
Key Management Module
type KeyManager struct {
StoragePath string
}
func (k *KeyManager) GenerateKey(name string) (*KeyPair, error)
func (k *KeyManager) ImportKey(name, keyPath string) error
func (k *KeyManager) ExportKey(name, destPath string) error
func (k *KeyManager) ListKeys() ([]KeyInfo, error)
4. Storage Layer (Data Persistence)
File Structure
~/.hostkeeper/
├── config.yaml # App configuration
├── hosts.json # Saved hosts/connections
├── keys.json # SSH keys store
├── snippets.json # Command snippets
└── exported/ # Exported configurations
└── hostkeeper-backup-2024-06-22.json
Data Structures
hosts.json:
{
"hosts": [
{
"id": "server1",
"name": "Production Server",
"hostname": "192.168.1.100",
"port": 22,
"username": "admin",
"auth": {
"type": "key",
"key_id": "default_key",
"password": null
},
"tags": ["production", "linux"],
"created_at": "2024-06-22T10:00:00Z"
}
]
}
keys.json:
{
"keys": [
{
"id": "default_key",
"name": "Default Key",
"private_key": "-----BEGIN RSA PRIVATE KEY-----...",
"public_key": "ssh-rsa AAAA...",
"comment": "Generated by hostkeeper",
"created_at": "2024-06-22T10:00:00Z"
}
]
}
config.yaml:
version: "1.0"
default_port: 22
connection_timeout: 30
sftp_timeout: 60
log_level: info
storage:
encrypt: false
compression: false
terminal:
color_scheme: default
font_size: medium
Key User Workflows
Workflow 1: Quick SSH Connection (Command-based)
$ hostkeeper connect production
Flow:
- CLI parses command → validates host exists
- Core Engine loads host config from
hosts.json - Establishes SSH connection with native terminal
- User gets native terminal experience
- On exit: connection cleanup, return to CLI
Error Handling:
- Host not found → Show available hosts
- Connection failed → User-friendly error with troubleshooting tips
- Auth failed → Prompt for password/key selection
Workflow 2: Add New Host (Interactive)
$ hostkeeper add
Interactive Prompts:
Hostname: staging-server
IP Address: 192.168.1.50
Port: [22]
Username: admin
Auth Method: [1] Password [2] SSH Key → 2
Tags: staging, linux, development
Flow:
- Collect host information interactively
- Validate for duplicates
- Generate UUID for host
- Append to
hosts.json - Offer connection test
Workflow 3: SFTP File Transfer
Phase 1 (MVP) - Native SFTP
$ hostkeeper sftp production
sftp> put /local/file.txt /remote/path/
sftp> get /remote/file.zip /local/backup/
sftp> ls -la /var/www/
Phase 2 - Custom TUI Browser
┌────────────────────┬────────────────────┐
│ Local Files │ Remote Files │
│ │ │
│ 📁 Documents/ │ 📁 /var/www/ │
│ 📄 file.txt │ 📄 index.html │
│ 📄 backup.zip │ 📄 config.php │
│ │ │
│ [Upload] [Refresh] │ [Download] [Delete]│
└────────────────────┴────────────────────┘
Workflow 4: Export/Import Credentials
$ hostkeeper export backup-2024-06.json
$ hostkeeper import backup-2024-06.json
Export Flow:
- Read all JSON files
- Combine into single structure
- Phase 1: Plain JSON export
- Phase 2: AES-256 encryption with password
- Write export file with summary
Export Structure:
{
"version": "1.0",
"exported_at": "2024-06-22T16:00:00Z",
"data": {
"hosts": [...],
"keys": [...],
"snippets": [...],
"config": {...}
}
}
Import Flow:
- Validate import file format
- Phase 2: Decrypt with password
- Merge options: replace/merge/skip
- Update local JSON files
- Show import summary
Security Architecture
Phase 1 Security (MVP)
File Security:
- File permissions: 0600 (owner read/write only)
- Environment variable protection
- No password/key logging in errors
- Sensitive data filtering from logs
- Plain text storage (Phase 2 encryption)
Security Measures:
// Proper file permissions
func SaveConfig(data []byte, path string) error {
err := os.WriteFile(path, data, 0600)
if err != nil {
return err
}
// Validate permissions
info, _ := os.Stat(path)
if info.Mode().Perm() != 0600 {
os.Chmod(path, 0600)
}
return nil
}
Phase 2 Security (Enhanced)
Encryption Layer:
type EncryptionManager struct {
MasterPassword []byte
Salt []byte
}
func (e *EncryptionManager) EncryptData(data []byte) ([]byte, error) {
// PBKDF2 key derivation
key := pbkdf2.Key(e.MasterPassword, e.Salt, 100000, 32, sha256.New)
// AES-256-GCM encryption
block, err := aes.NewCipher(key)
gcm, err := cipher.NewGCM(block)
// Encrypt with random nonce
nonce := make([]byte, gcm.NonceSize())
rand.Read(nonce)
ciphertext := gcm.Seal(nonce, nonce, data, nil)
return ciphertext, nil
}
Encrypted Storage Format:
{
"version": "2.0",
"encryption": "AES-256-GCM",
"iterations": 100000,
"salt": "base64_encoded_salt",
"data": "encrypted_base64_data"
}
Security Best Practices
Memory Security:
// Clear sensitive data from memory
func ClearSensitiveData(data []byte) {
for i := range data {
data[i] = 0
}
}
defer ClearSensitiveData(password)
Logging Security:
// Filter sensitive information
func sanitizeSensitiveData(input string) string {
sensitivePatterns := []string{
`password["']?\s*[:=]\s*["']?[^\s"']+`,
`private_key["']?\s*[:=]\s*["']?.+?["']?`,
}
for _, pattern := range sensitivePatterns {
re := regexp.MustCompile(pattern)
input = re.ReplaceAllString(input, "[REDACTED]")
}
return input
}
Error Handling Strategy
Connection Error Handling
type ConnectionError struct {
Type string // "auth", "network", "timeout", "config"
Message string
Details string
Hints []string
}
func HandleSSHError(err error) *ConnectionError {
switch {
case strings.Contains(err.Error(), "connection refused"):
return &ConnectionError{
Type: "network",
Message: "Cannot connect to server",
Details: err.Error(),
Hints: []string{"Check if server is running", "Verify firewall rules"},
}
case strings.Contains(err.Error(), "authentication failed"):
return &ConnectionError{
Type: "auth",
Message: "Authentication failed",
Details: err.Error(),
Hints: []string{"Verify username/password", "Check SSH key permissions"},
}
// ... additional cases
}
}
Error Display Format:
❌ Connection Error: Authentication failed
Details: ssh: handshake failed: ssh: unable to authenticate
Possible solutions:
1. Verify username: admin
2. Check SSH key is loaded: hostkeeper key list
3. Test connection manually: ssh admin@192.168.1.100
Use 'hostkeeper edit production' to update credentials
Critical Error Scenarios
Scenario 1: Corrupted Configuration File
❌ Configuration Error: Invalid hosts.json
Details: hosts.json contains corrupted data at line 23
Recovery options:
1. Restore from backup: ~/.hostkeeper/hosts.json.backup
2. Import from export: hostkeeper import backup.json
3. Reset configuration: hostkeeper reset --force (⚠️ This will delete all data)
Action needed: Configuration is unusable. Please choose recovery option.
Scenario 2: SSH Key Permission Issues
⚠️ Security Warning: Insecure key permissions
File: ~/.ssh/id_rsa has permissions 644
Recommended: 600 (owner read/write only)
Auto-fix command:
$ chmod 600 ~/.ssh/id_rsa
Continue anyway? [y/N]
Cross-Platform Strategy
Platform Support
Primary Platforms:
- Linux (x86_64, ARM)
- macOS (x86_64, ARM64)
- Windows (x86_64)
- Termux/Android (ARM)
Build Strategy:
- Go cross-compilation for all platforms
- Single binary distribution
- Platform-specific optimizations
Platform-Specific Handling:
Linux/Unix:
- Standard permission handling
- Native terminal integration
- System configuration paths
macOS:
- Keychain integration (optional)
- Proper terminal sizing
- macOS-specific paths
Windows:
- Windows Terminal support
- Path handling (backslash vs forward slash)
- Registry integration (optional)
Termux/Android:
- File permissions workaround
- Storage location adaptation
- Terminal limitations handling
Technology Stack
Core Libraries
SSH/SFTP:
golang.org/x/crypto/ssh- SSH protocol implementationpkg.sftp.io/ssh- SFTP client implementation
CLI Framework:
github.com/spf13/cobra- Command frameworkgithub.com/spf13/viper- Configuration management
TUI Framework:
github.com/charmbracelet/bubbletea- TUI frameworkgithub.com/charmbracelet/lipgloss- Styling
Encryption:
crypto/aes- AES encryption (Phase 2)crypto/cipher- Cipher implementationscrypto/pbkdf2- Key derivation
Utilities:
encoding/json- JSON handlinggopkg.in/yaml.v3- YAML supportgithub.com/google/uuid- UUID generation
Implementation Phases
Phase 1: MVP Foundation (3-4 weeks)
Core Functionality:
- Basic SSH connections (native terminal)
- Host management (add, list, edit, delete)
- SSH key generation and import
- Export/Import (plain JSON)
- Native SFTP client
- Basic TUI for host management
Deliverables:
- Working CLI with core commands
- JSON-based storage
- Cross-platform binary builds
- Basic documentation
Phase 2: Enhanced Features (4-6 weeks)
TUI Enhancement:
- Rich TUI interface for all operations
- Dual-pane SFTP browser
- Key management UI
- Snippet manager
- Connection snippets execution
Security Enhancement:
- AES-256 encryption for storage
- Master password protection
- Secure credential export/import
- Enhanced error messages
User Experience:
- Shell completion
- Configuration profiles
- Theme support
- Keyboard shortcuts
Phase 3: Advanced Features (Future)
Cloud Sync:
- User account system
- Encrypted cloud storage
- Real-time multi-device sync
- Conflict resolution
Advanced Terminal:
- Custom terminal emulator
- Multiple session management
- Tab support
- Advanced text selection
Integration:
- Web UI (optional)
- API access
- Plugin system
- Third-party integrations
Development Guidelines
Code Quality
Standards:
- Follow Go best practices and idioms
- Comprehensive error handling
- Logging for debugging
- Code documentation
- Unit tests for critical functions
Security Considerations:
- No hardcoded credentials
- Input validation and sanitization
- Secure default configurations
- Regular security audits
Testing Strategy
Unit Testing:
- Core engine functions
- SSH connection logic
- Encryption/decryption
- Configuration management
Integration Testing:
- End-to-end SSH connections
- SFTP operations
- Export/Import functionality
- Cross-platform compatibility
Manual Testing:
- Cross-platform testing (Linux, macOS, Windows, Termux)
- User experience validation
- Performance testing
- Security validation
Success Criteria
MVP Success Metrics
- ✅ Can establish SSH connections to remote servers
- ✅ Can manage multiple hosts with different auth methods
- ✅ Can perform SFTP operations (Phase 1: native, Phase 2: TUI)
- ✅ Can export/import credentials across devices
- ✅ Works on Linux, macOS, Windows, and Termux
- ✅ Secure credential storage with proper permissions
- ✅ User-friendly error messages and help text
Phase 2 Success Metrics
- ✅ Rich TUI interface for all operations
- ✅ Encrypted credential storage
- ✅ Dual-pane SFTP browser
- ✅ Shell completion and documentation
- ✅ Enhanced user experience
Risks and Mitigation
Technical Risks
Risk: SSH terminal emulation complexity Mitigation: Progressive approach - native terminal first, custom TUI later
Risk: Cross-platform compatibility issues Mitigation: Extensive testing on all platforms, Go's cross-compilation
Risk: Performance issues with TUI Mitigation: Framework selection (Bubble Tea), performance testing
Security Risks
Risk: Credential exposure Mitigation: Proper file permissions, memory clearing, secure defaults
Risk: Key management complexity Mitigation: Clear user guidance, validation, secure defaults
Risk: Encryption implementation bugs Mitigation: Use standard libraries, security audits, testing
Conclusion
Hostkeeper aims to provide a comprehensive SSH/SFTP management solution with cross-platform support and secure credential management. The progressive development approach ensures rapid delivery of core functionality while maintaining a clear path to advanced features.
The hybrid CLI/TUI approach balances power user needs with accessibility, while the phased implementation allows for iterative improvement based on user feedback.
Document Status: Approved for implementation Next Steps: Implementation planning using writing-plans skill
Sources: