refactor: move V1 code into v1/ subdirectory
- git mv cmd/ internal/ pkg/ test/ go.mod go.sum Makefile build.sh docs/ v1/ - Create v1/README.md with V1 documentation - Update root README for V1 + V2 structure - V1 still builds (cd v1 && go build ./cmd/hostkeeper) and 105 tests pass - Root is now clean for V2 development
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ConnectionError represents SSH connection errors with helpful hints
|
||||
type ConnectionError struct {
|
||||
Type string // "auth", "network", "timeout", "config", "unknown"
|
||||
Message string
|
||||
Details string
|
||||
Hints []string
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *ConnectionError) Error() string {
|
||||
return fmt.Sprintf("Connection Error (%s): %s\nDetails: %s", e.Type, e.Message, e.Details)
|
||||
}
|
||||
|
||||
// NewConnectionError creates a new ConnectionError
|
||||
func NewConnectionError(errType, message, details string, hints []string) *ConnectionError {
|
||||
return &ConnectionError{
|
||||
Type: errType,
|
||||
Message: message,
|
||||
Details: details,
|
||||
Hints: hints,
|
||||
}
|
||||
}
|
||||
|
||||
// HandleSSHError processes SSH errors and returns user-friendly errors
|
||||
func HandleSSHError(err error) *ConnectionError {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
errStr := err.Error()
|
||||
|
||||
switch {
|
||||
case strings.Contains(errStr, "connection refused"):
|
||||
return &ConnectionError{
|
||||
Type: "network",
|
||||
Message: "Cannot connect to server",
|
||||
Details: errStr,
|
||||
Hints: []string{"Check if server is running", "Verify firewall rules", "Confirm hostname and port"},
|
||||
}
|
||||
|
||||
case strings.Contains(errStr, "authentication failed"), strings.Contains(errStr, "unable to authenticate"):
|
||||
return &ConnectionError{
|
||||
Type: "auth",
|
||||
Message: "Authentication failed",
|
||||
Details: errStr,
|
||||
Hints: []string{"Verify username and password", "Check SSH key is loaded", "Test with native SSH client"},
|
||||
}
|
||||
|
||||
case strings.Contains(errStr, "timeout"), strings.Contains(errStr, "timed out"):
|
||||
return &ConnectionError{
|
||||
Type: "timeout",
|
||||
Message: "Connection timeout",
|
||||
Details: errStr,
|
||||
Hints: []string{"Check network connectivity", "Try increasing timeout", "Verify server is reachable"},
|
||||
}
|
||||
|
||||
case strings.Contains(errStr, "no such host"), strings.Contains(errStr, "hostname"):
|
||||
return &ConnectionError{
|
||||
Type: "config",
|
||||
Message: "Invalid hostname",
|
||||
Details: errStr,
|
||||
Hints: []string{"Verify hostname spelling", "Check DNS resolution", "Try IP address instead"},
|
||||
}
|
||||
|
||||
case strings.Contains(errStr, "permission denied"):
|
||||
return &ConnectionError{
|
||||
Type: "auth",
|
||||
Message: "Permission denied",
|
||||
Details: errStr,
|
||||
Hints: []string{"Check user permissions on server", "Verify account is not locked", "Check authentication method"},
|
||||
}
|
||||
|
||||
default:
|
||||
return &ConnectionError{
|
||||
Type: "unknown",
|
||||
Message: "Connection failed",
|
||||
Details: errStr,
|
||||
Hints: []string{"Check host configuration", "Verify network settings", "Test with standard SSH client"},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FormatConnectionError formats connection error for user-friendly display
|
||||
func FormatConnectionError(err *ConnectionError) string {
|
||||
var output strings.Builder
|
||||
|
||||
output.WriteString(fmt.Sprintf("❌ Connection Error: %s\n\n", err.Message))
|
||||
output.WriteString(fmt.Sprintf("Details: %s\n\n", err.Details))
|
||||
|
||||
if len(err.Hints) > 0 {
|
||||
output.WriteString("Possible solutions:\n")
|
||||
for i, hint := range err.Hints {
|
||||
output.WriteString(fmt.Sprintf(" %d. %s\n", i+1, hint))
|
||||
}
|
||||
}
|
||||
|
||||
return output.String()
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Error codes
|
||||
const (
|
||||
ErrHostNotFound = "HOST_NOT_FOUND"
|
||||
ErrAuthFailed = "AUTH_FAILED"
|
||||
ErrConnectionTimeout = "CONNECTION_TIMEOUT"
|
||||
ErrInvalidConfig = "INVALID_CONFIG"
|
||||
ErrKeyNotFound = "KEY_NOT_FOUND"
|
||||
ErrPermissionDenied = "PERMISSION_DENIED"
|
||||
ErrFileCorrupted = "FILE_CORRUPTED"
|
||||
ErrInvalidCredentials = "INVALID_CREDENTIALS"
|
||||
)
|
||||
|
||||
// AppError represents an application error with code, message, cause, and hints
|
||||
type AppError struct {
|
||||
Code string
|
||||
Message string
|
||||
Cause error
|
||||
Hints []string
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *AppError) Error() string {
|
||||
if e.Cause != nil {
|
||||
return fmt.Sprintf("[%s] %s: %v", e.Code, e.Message, e.Cause)
|
||||
}
|
||||
return fmt.Sprintf("[%s] %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying cause for use with errors.Is/errors.As
|
||||
func (e *AppError) Unwrap() error {
|
||||
return e.Cause
|
||||
}
|
||||
|
||||
// NewAppError creates a new application error
|
||||
func NewAppError(code, message string, cause error, hints []string) *AppError {
|
||||
return &AppError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Cause: cause,
|
||||
Hints: hints,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Host represents an SSH host connection configuration
|
||||
type Host struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Hostname string `json:"hostname"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
Auth AuthConfig `json:"auth"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
|
||||
}
|
||||
|
||||
// AuthConfig represents authentication configuration
|
||||
type AuthConfig struct {
|
||||
Type string `json:"type"` // "password", "key", "both"
|
||||
Password string `json:"password,omitempty"`
|
||||
KeyID string `json:"key_id,omitempty"` // Reference to KeyPair ID
|
||||
}
|
||||
|
||||
// KeyPair represents an SSH key pair
|
||||
type KeyPair struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"` // "rsa", "ed25519", "ecdsa"
|
||||
PrivateKey string `json:"private_key"`
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
Passphrase string `json:"passphrase,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Snippet represents a command snippet
|
||||
type Snippet struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Profile represents a named configuration profile
|
||||
type Profile struct {
|
||||
Name string `json:"name"`
|
||||
Theme string `json:"theme"`
|
||||
DefaultGroup string `json:"default_group,omitempty"`
|
||||
DefaultAuth string `json:"default_auth,omitempty"` // "password", "key", "both"
|
||||
Editor string `json:"editor,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
}
|
||||
|
||||
// AppConfig represents the application configuration
|
||||
type AppConfig struct {
|
||||
Version string `json:"version"`
|
||||
DefaultPort int `json:"default_port"`
|
||||
ConnectionTimeout int `json:"connection_timeout"` // in seconds
|
||||
Theme string `json:"theme"`
|
||||
Editor string `json:"editor"`
|
||||
AutoSync bool `json:"auto_sync"`
|
||||
SyncProvider string `json:"sync_provider,omitempty"`
|
||||
|
||||
// Profiles
|
||||
Profiles []Profile `json:"profiles,omitempty"`
|
||||
ActiveProfile string `json:"active_profile,omitempty"`
|
||||
|
||||
// Security
|
||||
EncryptionEnabled bool `json:"encryption_enabled"`
|
||||
PasswordHash string `json:"password_hash,omitempty"` // SHA-256 hash for verification
|
||||
KnownHostsFile string `json:"known_hosts_file,omitempty"`
|
||||
}
|
||||
|
||||
// KnownHost represents a verified host key
|
||||
type KnownHost struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Port int `json:"port"`
|
||||
KeyType string `json:"key_type"` // "ssh-rsa", "ssh-ed25519", etc.
|
||||
KeyHash string `json:"key_hash"` // Base64-encoded host key
|
||||
AddedAt time.Time `json:"added_at"`
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default application configuration
|
||||
func DefaultConfig() *AppConfig {
|
||||
return &AppConfig{
|
||||
Version: "1.0.0",
|
||||
DefaultPort: 22,
|
||||
ConnectionTimeout: 30,
|
||||
Theme: "dark",
|
||||
Editor: "vim",
|
||||
AutoSync: false,
|
||||
EncryptionEnabled: false,
|
||||
Profiles: []Profile{
|
||||
{
|
||||
Name: "default",
|
||||
Theme: "dark",
|
||||
},
|
||||
},
|
||||
ActiveProfile: "default",
|
||||
}
|
||||
}
|
||||
|
||||
// GetProfile returns a profile by name
|
||||
func (c *AppConfig) GetProfile(name string) *Profile {
|
||||
for i := range c.Profiles {
|
||||
if c.Profiles[i].Name == name {
|
||||
return &c.Profiles[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetActiveProfile returns the active profile
|
||||
func (c *AppConfig) GetActiveProfile() *Profile {
|
||||
return c.GetProfile(c.ActiveProfile)
|
||||
}
|
||||
|
||||
// AddProfile adds a new profile
|
||||
func (c *AppConfig) AddProfile(p Profile) {
|
||||
c.Profiles = append(c.Profiles, p)
|
||||
}
|
||||
|
||||
// RemoveProfile removes a profile by name
|
||||
func (c *AppConfig) RemoveProfile(name string) {
|
||||
for i := range c.Profiles {
|
||||
if c.Profiles[i].Name == name {
|
||||
c.Profiles = append(c.Profiles[:i], c.Profiles[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user