11 Commits

Author SHA1 Message Date
ed6ea2939a fix(ci): correct version injection in build workflow
- Change -X flag from cmd.version to cmd.AppVersion
- This fixes version display showing 'dev' instead of actual version
- Binaries will now display correct version when built via GitHub Actions
2025-09-13 12:20:41 +02:00
45eccabc23 fix: replace Ctrl+Enter with Ctrl+S in forms to avoid terminal conflicts 2025-09-13 11:53:45 +02:00
2425695992 docs: update README 2025-09-13 11:53:45 +02:00
306f38e862 feat: show error when move requires includes but none found 2025-09-13 11:53:45 +02:00
3c627a5d21 feat: add port forwarding history persistence 2025-09-13 11:53:45 +02:00
71bf8ea2bb feat: add direct host connection via sshm <host> with history tracking 2025-09-13 11:53:45 +02:00
8c6f3b01ef feat: centralize history storage in config directory
Automatically migrates existing ~/.ssh/sshm_history.json to platform-appropriate config location
2025-09-13 11:53:45 +02:00
aa6be1d92d fix(cmd): export variables for test accessibility
Export rootCmd->RootCmd and appVersion->AppVersion to fix test compilation errors. Update all references across cmd package and tests.
2025-09-13 11:53:45 +02:00
9bb44da18b build: strip 'v' prefix from version tag for binary
- Remove the 'v' prefix from the Git tag before injecting the version into the built binary
- Ensures the version string in the CLI does not include a leading 'v' (e.g. '1.2.3' instead of 'v1.2.3')
2025-09-13 11:53:45 +02:00
77b2b8fd22 feat: add move command to relocate SSH hosts between config files
- Add 'move' command with interactive file selector
- Implement atomic host moving between SSH config files
- Support for configs with include directives
- Add comprehensive error handling and validation
- Update help screen with improved two-column layout
2025-09-13 11:53:45 +02:00
5c832ce26f feat: add automatic version update checking and notifications
- Add internal/version module for GitHub release checking
- Integrate async version check in Bubble Tea UI
- Display update notification in main interface
- Add version check to --version/-v command output
- Include comprehensive version comparison and error handling
- Add unit tests for version parsing and comparison logic
2025-09-13 11:53:45 +02:00
8 changed files with 210 additions and 37 deletions

View File

@@ -71,9 +71,9 @@ jobs:
# Remove 'v' prefix if present for version injection
VERSION_CLEAN=${VERSION#v}
if [ "${{ matrix.goos }}" = "windows" ]; then
go build -ldflags="-s -w -X github.com/Gu1llaum-3/sshm/cmd.version=${VERSION_CLEAN}" -o dist/sshm-${{ matrix.suffix }}.exe .
go build -ldflags="-s -w -X github.com/Gu1llaum-3/sshm/cmd.AppVersion=${VERSION_CLEAN}" -o dist/sshm-${{ matrix.suffix }}.exe .
else
go build -ldflags="-s -w -X github.com/Gu1llaum-3/sshm/cmd.version=${VERSION_CLEAN}" -o dist/sshm-${{ matrix.suffix }} .
go build -ldflags="-s -w -X github.com/Gu1llaum-3/sshm/cmd.AppVersion=${VERSION_CLEAN}" -o dist/sshm-${{ matrix.suffix }} .
fi
- name: Create archive

187
README.md
View File

@@ -25,35 +25,29 @@ SSHM is a beautiful command-line tool that transforms how you manage and connect
## ✨ Features
### 🎯 **Core Features**
### 🚀 **Core Capabilities**
- **🎨 Beautiful TUI Interface** - Navigate your SSH hosts with an elegant, interactive terminal UI
- **⚡ Quick Connect** - Connect to any host instantly
- **🔄 Port Forwarding** - Easy setup for Local, Remote, and Dynamic (SOCKS) forwarding
- **📝Easy Management** - Add, edit, and manage SSH configurations seamlessly
- **⚡ Quick Connect** - Connect to any host instantly through the TUI or the CLI with `sshm <host>`
- **🔄 Port Forwarding** - Easy setup for Local, Remote, and Dynamic (SOCKS) forwarding with history persistence
- **📝 Easy Management** - Add, edit, move, and manage SSH configurations seamlessly
- **🏷️ Tag Support** - Organize your hosts with custom tags for better categorization
- **🔍 Smart Search** - Find hosts quickly with built-in filtering and search
- **📝 Real-time Status** - Live SSH connectivity indicators with asynchronous ping checks and color-coded status
- **🔔 Smart Updates** - Automatic version checking with update notifications
- **📈 Connection History** - Track your SSH connections with last login timestamps
### 🛠️ **Technical Features**
- **🔒 Secure** - Works directly with your existing `~/.ssh/config` file
- **📁 Custom Config Support** - Use any SSH configuration file with the `-c` flag
- **📂 SSH Include Support** - Full support for SSH Include directives to organize configurations across multiple files
- **⚙️ SSH Options Support** - Add any SSH configuration option through intuitive forms
- **🔄 Automatic Conversion** - Seamlessly converts between command-line and config formats
### 🛠️ **Management Operations**
- **Add new SSH hosts** with interactive forms
- **Edit existing configurations** in-place
- **Delete hosts** with confirmation prompts
- **Port forwarding setup** with intuitive interface for Local (-L), Remote (-R), and Dynamic (-D) forwarding
- **Backup configurations** automatically before changes
- **Validate settings** to prevent configuration errors
- **ProxyJump support** for secure connection tunneling through bastion hosts
- **SSH Options management** - Add any SSH option with automatic format conversion
- **Full SSH compatibility** - Maintains compatibility with standard SSH tools
### 🎮 **User Experience**
- **Zero configuration** - Works out of the box with your existing SSH setup
- **Keyboard shortcuts** for power users
- **Cross-platform** - Supports Linux, macOS (Intel & Apple Silicon), and Windows
- **Lightweight** - Single binary with no dependencies
- **🔄 Automatic Backups** - Backup configurations automatically before changes
- **✅ Validation** - Prevent configuration errors with built-in validation
- **🔗 ProxyJump Support** - Secure connection tunneling through bastion hosts
- **⌨️ Keyboard Shortcuts** - Power user navigation with vim-like shortcuts
- **🌐 Cross-platform** - Supports Linux, macOS (Intel & Apple Silicon), and Windows
- **⚡ Lightweight** - Single binary with no dependencies, zero configuration required
## 🚀 Quick Start
@@ -105,10 +99,17 @@ sshm
- `a` - Add new host
- `e` - Edit selected host
- `d` - Delete selected host
- `m` - Move host to another config file (requires SSH Include directives)
- `f` - Port forwarding setup
- `q` - Quit
- `/` - Search/filter hosts
**Real-time Status Indicators:**
- 🟢 **Online** - Host is reachable via SSH
- 🟡 **Connecting** - Currently checking host connectivity
- 🔴 **Offline** - Host is unreachable or SSH connection failed
-**Unknown** - Connectivity status not yet determined
**Sorting & Filtering:**
- `s` - Switch between sorting modes (name ↔ last login)
- `n` - Sort by **name** (alphabetical)
@@ -158,6 +159,7 @@ SSHM provides an intuitive interface for setting up SSH port forwarding. Press `
- Configure ports and addresses with guided forms
- Optional bind address configuration (defaults to 127.0.0.1)
- Real-time validation of port numbers and addresses
- **Port forwarding history** - Save frequently used configurations for quick reuse
- Connect automatically with configured forwarding options
**Troubleshooting Port Forwarding:**
@@ -218,9 +220,15 @@ SSHM provides both command-line operations and an interactive TUI interface:
# Launch interactive TUI mode for browsing and connecting to hosts
sshm
# Connect directly to a specific host (with history tracking)
sshm my-server
# Launch TUI with custom SSH config file
sshm -c /path/to/custom/ssh_config
# Connect directly with custom SSH config file
sshm my-server -c /path/to/custom/ssh_config
# Add a new host using interactive form
sshm add
@@ -236,13 +244,42 @@ sshm edit my-server
# Edit host with custom SSH config file
sshm edit my-server -c /path/to/custom/ssh_config
# Show version information
# Move a host to another SSH config file (requires Include directives)
sshm move my-server
# Move host with custom SSH config file (requires Include directives)
sshm move my-server -c /path/to/custom/ssh_config
# Search for hosts (interactive filter)
sshm search
# Show version information (includes update check)
sshm --version
# Show help and available commands
sshm --help
```
### Direct Host Connection
SSHM supports direct connection to hosts via the command line, making it easy to integrate into your existing workflow:
```bash
# Connect directly to any configured host
sshm production-server
sshm db-staging
sshm web-01
# All direct connections are tracked in your history
# Use the TUI to see your most recently connected hosts
```
**Features of Direct Connection:**
- **Instant connection** - No TUI navigation required
- **History tracking** - All connections are recorded with timestamps
- **Error handling** - Clear messages if host doesn't exist or configuration issues
- **Config file support** - Works with custom config files using `-c` flag
### Backup Configuration
SSHM automatically creates backups of your SSH configuration files before making any changes to ensure your configurations are safe.
@@ -257,6 +294,10 @@ SSHM automatically creates backups of your SSH configuration files before making
- Stored separately to avoid SSH Include conflicts
- Easy manual recovery if needed
**Additional Storage:**
- **Connection History**: Stored in the same config directory for persistent tracking
- **Port Forwarding History**: Saved configurations for quick reuse of common forwarding setups
**Quick Recovery:**
```bash
# Unix/Linux/macOS
@@ -277,8 +318,96 @@ sshm -c /path/to/custom/ssh_config
# Use custom config file with commands
sshm add hostname -c /path/to/custom/ssh_config
sshm edit hostname -c /path/to/custom/ssh_config
sshm move hostname -c /path/to/custom/ssh_config
```
### Advanced Features
#### Host Movement Between Config Files
SSHM provides a powerful `move` command to relocate SSH hosts between different configuration files. **This feature requires SSH Include directives to be present in your SSH configuration.**
```bash
# Move a host to another config file (requires Include directives)
sshm move my-server
# Move with custom config file (requires Include directives)
sshm move my-server -c /path/to/custom/ssh_config
```
**⚠️ Important Requirements:**
- **SSH Include directives must be present** in your SSH config file (either `~/.ssh/config` or the file specified with `-c`)
- The config file must contain `Include` statements referencing other SSH configuration files
- Without Include directives, the move command will display an error message
**Features:**
- **Interactive file selector** - Choose destination config file from Include directives
- **Include support** - Works seamlessly with SSH Include directives structure
- **Atomic operations** - Safe host movement with automatic backups
- **Validation** - Prevents conflicts and ensures configuration integrity
- **Error handling** - Clear messages when Include files are needed but not found
**Use Cases:**
- Reorganize hosts from main config to specialized include files
- Move development hosts to separate environment-specific configs
- Consolidate configurations for better organization
**Example Setup Required:**
Your main SSH config file must contain Include directives like:
```ssh
# ~/.ssh/config
Include ~/.ssh/config.d/*
Include work-servers.conf
Include projects/*.conf
Host personal-server
HostName personal.example.com
User myuser
```
#### Real-time Connectivity Status
SSHM features asynchronous SSH connectivity checking that provides visual indicators of host availability:
**Status Indicators:**
- 🟢 **Online** - SSH connection successful (shows response time)
- 🟡 **Connecting** - Currently testing connectivity
- 🔴 **Offline** - SSH connection failed or host unreachable
-**Unknown** - Status not yet determined
**Features:**
- **Non-blocking checks** - Status updates happen in the background
- **Response time tracking** - See connection latency for online hosts
- **Automatic refresh** - Status indicators update continuously
- **Error details** - Detailed error information for failed connections
#### Automatic Update Checking
SSHM includes built-in version checking that notifies you of available updates:
**Features:**
- **Background checking** - Version check happens asynchronously
- **Release notifications** - Clear indicators when updates are available
- **Pre-release detection** - Identifies beta and development versions
- **GitHub integration** - Direct links to release pages
- **Non-intrusive** - Updates don't interrupt your workflow
**Update notifications appear:**
- In the main TUI interface as a subtle notification
- In the `sshm --version` command output
- Only when a newer stable version is available
#### Port Forwarding History
SSHM remembers your port forwarding configurations for easy reuse:
**Features:**
- **Automatic saving** - Successful forwarding setups are saved automatically
- **Quick reuse** - Previously used configurations appear as suggestions
- **Per-host history** - Forwarding history is tracked per SSH host
- **All forward types** - Supports Local (-L), Remote (-R), and Dynamic (-D) forwarding history
- **Persistent storage** - History survives application restarts
### Platform-Specific Notes
**Windows:**
@@ -449,20 +578,29 @@ sshm/
│ ├── root.go # Root command and interactive mode
│ ├── add.go # Add host command
│ ├── edit.go # Edit host command
│ ├── move.go # Move host command
│ └── search.go # Search command
├── internal/
│ ├── config/ # SSH configuration management
│ │ └── ssh.go # Config parsing and manipulation
│ ├── connectivity/ # SSH connectivity checking
│ │ └── ping.go # Asynchronous SSH ping functionality
│ ├── history/ # Connection history tracking
│ │ ── history.go # History management and last login tracking
│ │ ── history.go # History management and last login tracking
│ │ └── port_forward_test.go # Port forwarding history tests
│ ├── version/ # Version checking and updates
│ │ ├── version.go # GitHub release checking and version comparison
│ │ └── version_test.go # Version parsing and comparison tests
│ ├── ui/ # Terminal UI components (Bubble Tea)
│ │ ├── tui.go # Main TUI interface and program setup
│ │ ├── model.go # Core TUI model and state
│ │ ├── update.go # Message handling and state updates
│ │ ├── view.go # UI rendering and layout
│ │ ├── table.go # Host list table component
│ │ ├── table.go # Host list table component with status indicators
│ │ ├── add_form.go # Add host form interface
│ │ ├── edit_form.go# Edit host form interface
│ │ ├── move_form.go# Move host form interface
│ │ ├── port_forward_form.go # Port forwarding setup with history
│ │ ├── styles.go # Lip Gloss styling definitions
│ │ ├── sort.go # Sorting and filtering logic
│ │ └── utils.go # UI utility functions
@@ -490,6 +628,7 @@ sshm/
- [Bubble Tea](https://github.com/charmbracelet/bubbletea) - TUI framework
- [Bubbles](https://github.com/charmbracelet/bubbles) - TUI components
- [Lipgloss](https://github.com/charmbracelet/lipgloss) - Styling
- [Go Crypto SSH](https://golang.org/x/crypto/ssh) - SSH connectivity checking
## 📦 Releases

View File

@@ -4,9 +4,10 @@ import (
"os"
"os/user"
"path/filepath"
"strings"
"github.com/Gu1llaum-3/sshm/internal/config"
"github.com/Gu1llaum-3/sshm/internal/validation"
"strings"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
@@ -148,8 +149,8 @@ func (m *addFormModel) Update(msg tea.Msg) (*addFormModel, tea.Cmd) {
case "ctrl+c", "esc":
return m, func() tea.Msg { return addFormCancelMsg{} }
case "ctrl+enter":
// Allow submission from any field with Ctrl+Enter
case "ctrl+s":
// Allow submission from any field with Ctrl+S (Save)
return m, m.submitForm()
case "tab", "shift+tab", "enter", "up", "down":
@@ -238,7 +239,7 @@ func (m *addFormModel) View() string {
b.WriteString("\n\n")
}
b.WriteString(m.styles.FormHelp.Render("Tab/Shift+Tab: navigate • Enter on last field: submit • Ctrl+Enter: submit • Ctrl+C/Esc: cancel"))
b.WriteString(m.styles.FormHelp.Render("Tab/Shift+Tab: navigate • Enter on last field: submit • Ctrl+S: save • Ctrl+C/Esc: cancel"))
b.WriteString("\n")
b.WriteString(m.styles.FormHelp.Render("* Required fields"))

View File

@@ -1,9 +1,10 @@
package ui
import (
"strings"
"github.com/Gu1llaum-3/sshm/internal/config"
"github.com/Gu1llaum-3/sshm/internal/validation"
"strings"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
@@ -139,8 +140,8 @@ func (m *editFormModel) Update(msg tea.Msg) (*editFormModel, tea.Cmd) {
case "ctrl+c", "esc":
return m, func() tea.Msg { return editFormCancelMsg{} }
case "ctrl+enter":
// Allow submission from any field with Ctrl+Enter
case "ctrl+s":
// Allow submission from any field with Ctrl+S (Save)
return m, m.submitEditForm()
case "tab", "shift+tab", "enter", "up", "down":
@@ -247,7 +248,7 @@ func (m *editFormModel) View() string {
b.WriteString("\n\n")
}
b.WriteString(m.styles.FormHelp.Render("Tab/Shift+Tab: navigate • Enter on last field: submit • Ctrl+Enter: submit • Ctrl+C/Esc: cancel"))
b.WriteString(m.styles.FormHelp.Render("Tab/Shift+Tab: navigate • Enter on last field: submit • Ctrl+S: save • Ctrl+C/Esc: cancel"))
b.WriteString("\n")
b.WriteString(m.styles.FormHelp.Render("* Required fields"))

View File

@@ -99,6 +99,10 @@ type Model struct {
height int
styles Styles
ready bool
// Error handling
errorMessage string
showingError bool
}
// updateTableStyles updates the table header border color based on focus state

View File

@@ -42,7 +42,7 @@ func NewMoveForm(hostName string, styles Styles, width, height int, configFile s
}
if len(files) == 0 {
return nil, fmt.Errorf("no other config files available to move host to")
return nil, fmt.Errorf("no includes found in SSH config file - move operation requires multiple config files")
}
// Create a custom file selector for move operation

View File

@@ -19,6 +19,7 @@ type (
pingResultMsg *connectivity.HostPingResult
versionCheckMsg *version.UpdateInfo
versionErrorMsg error
errorMsg string
)
// startPingAllCmd creates a command to ping all hosts concurrently
@@ -157,6 +158,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// as it might disrupt the user experience
return m, nil
case errorMsg:
// Handle general error messages
if string(msg) == "clear" {
m.showingError = false
m.errorMessage = ""
}
return m, nil
case addFormSubmitMsg:
if msg.err != nil {
// Show error in form
@@ -587,8 +596,13 @@ func (m Model) handleListViewKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
hostName := extractHostNameFromTableRow(selected[0]) // Extract hostname from first column
moveForm, err := NewMoveForm(hostName, m.styles, m.width, m.height, m.configFile)
if err != nil {
// Handle error - could show in UI, e.g., no other config files available
return m, nil
// Show error message to user
m.errorMessage = err.Error()
m.showingError = true
return m, func() tea.Msg {
time.Sleep(3 * time.Second) // Show error for 3 seconds
return errorMsg("clear")
}
}
m.moveForm = moveForm
m.viewMode = ViewMove

View File

@@ -72,6 +72,20 @@ func (m Model) renderListView() string {
components = append(components, updateStyle.Render(updateText))
}
// Add error message if there's one to show
if m.showingError && m.errorMessage != "" {
errorStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("9")). // Red color
Background(lipgloss.Color("1")). // Dark red background
Bold(true).
Padding(0, 1).
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("9")).
Align(lipgloss.Center)
components = append(components, errorStyle.Render("❌ "+m.errorMessage))
}
// Add the search bar with the appropriate style based on focus
searchPrompt := "Search (/ to focus): "
if m.searchMode {