mirror of
https://github.com/Gu1llaum-3/sshm.git
synced 2026-01-27 03:04:21 +01:00
Compare commits
9 Commits
3d746ec49a
...
v1.6.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
| ab5f430ee1 | |||
| 3da3a33530 | |||
| 12c1ab476c | |||
| e0e50ebfd0 | |||
| 947afb2bbe | |||
| fe529792e3 | |||
| 5ee623d054 | |||
| 09423287fd | |||
| 4767267387 |
136
.github/workflows/build.yml
vendored
Normal file
136
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
name: Build Binaries
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
release:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Linux AMD64
|
||||
- goos: linux
|
||||
goarch: amd64
|
||||
suffix: linux-amd64
|
||||
# Linux ARM64
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
suffix: linux-arm64
|
||||
# macOS AMD64 (Intel)
|
||||
- goos: darwin
|
||||
goarch: amd64
|
||||
suffix: darwin-amd64
|
||||
# macOS ARM64 (Apple Silicon)
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
suffix: darwin-arm64
|
||||
# Windows AMD64
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
suffix: windows-amd64
|
||||
# Windows ARM64
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
suffix: windows-arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23'
|
||||
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Build binary
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
CGO_ENABLED: 0
|
||||
run: |
|
||||
mkdir -p dist
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
# 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 .
|
||||
else
|
||||
go build -ldflags="-s -w -X github.com/Gu1llaum-3/sshm/cmd.version=${VERSION_CLEAN}" -o dist/sshm-${{ matrix.suffix }} .
|
||||
fi
|
||||
|
||||
- name: Create archive
|
||||
run: |
|
||||
cd dist
|
||||
if [ "${{ matrix.goos }}" = "windows" ]; then
|
||||
zip sshm-${{ matrix.suffix }}.zip sshm-${{ matrix.suffix }}.exe
|
||||
rm sshm-${{ matrix.suffix }}.exe
|
||||
else
|
||||
tar -czf sshm-${{ matrix.suffix }}.tar.gz sshm-${{ matrix.suffix }}
|
||||
rm sshm-${{ matrix.suffix }}
|
||||
fi
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sshm-${{ matrix.suffix }}
|
||||
path: |
|
||||
dist/sshm-${{ matrix.suffix }}.tar.gz
|
||||
dist/sshm-${{ matrix.suffix }}.zip
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: Prepare release assets
|
||||
run: |
|
||||
mkdir -p release
|
||||
find ./artifacts -name "*.tar.gz" -exec cp {} ./release/ \;
|
||||
find ./artifacts -name "*.zip" -exec cp {} ./release/ \;
|
||||
ls -la ./release/
|
||||
|
||||
- name: Check if pre-release
|
||||
id: check_prerelease
|
||||
run: |
|
||||
if [[ "${GITHUB_REF#refs/tags/}" == *"-beta"* ]] || [[ "${GITHUB_REF#refs/tags/}" == *"-alpha"* ]] || [[ "${GITHUB_REF#refs/tags/}" == *"-rc"* ]] || [[ "${GITHUB_REF#refs/tags/}" == *"-dev"* ]]; then
|
||||
echo "is_prerelease=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "is_prerelease=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ./release/*
|
||||
draft: false
|
||||
prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }}
|
||||
generate_release_notes: true
|
||||
name: ${{ github.ref_name }}${{ steps.check_prerelease.outputs.is_prerelease == 'true' && ' (Pre-release)' || '' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
39
.github/workflows/release.yml
vendored
39
.github/workflows/release.yml
vendored
@@ -1,39 +0,0 @@
|
||||
name: Release with GoReleaser
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
# Required for Homebrew tap updates
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Fetch full history for changelog generation
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23'
|
||||
cache: true
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: '~> v2'
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Token for updating Homebrew tap (create this secret in your repo settings)
|
||||
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
|
||||
135
.goreleaser.yaml
135
.goreleaser.yaml
@@ -1,135 +0,0 @@
|
||||
version: 2
|
||||
project_name: sshm
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go test ./...
|
||||
|
||||
builds:
|
||||
- id: sshm
|
||||
main: ./main.go
|
||||
binary: sshm
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- "386"
|
||||
- arm
|
||||
ignore:
|
||||
# Skip ARM for Windows (not commonly used)
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -X github.com/Gu1llaum-3/sshm/cmd.AppVersion={{.Version}}
|
||||
flags:
|
||||
- -trimpath
|
||||
|
||||
archives:
|
||||
- id: sshm
|
||||
formats: [ "tar.gz" ]
|
||||
# Use zip for Windows
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
formats: [ "zip" ]
|
||||
# Template for archive name
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_ {{- title .Os }}_ {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
files:
|
||||
- LICENSE
|
||||
- README.md
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
algorithm: sha256
|
||||
|
||||
changelog:
|
||||
use: github
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
- "^ci:"
|
||||
- "^chore:"
|
||||
- "^build:"
|
||||
groups:
|
||||
- title: Features
|
||||
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
|
||||
order: 0
|
||||
- title: Bug fixes
|
||||
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: Others
|
||||
order: 999
|
||||
|
||||
# Homebrew tap configuration (Formula pour CLI)
|
||||
brews:
|
||||
- name: sshm
|
||||
repository:
|
||||
owner: Gu1llaum-3
|
||||
name: homebrew-sshm
|
||||
# Token with repo permissions for your homebrew-sshm repo
|
||||
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
|
||||
commit_author:
|
||||
name: goreleaserbot
|
||||
email: bot@goreleaser.com
|
||||
commit_msg_template: "Brew formula update for {{ .ProjectName }} version {{ .Tag }}"
|
||||
homepage: "https://github.com/Gu1llaum-3/sshm"
|
||||
description: "A modern SSH connection manager for your terminal"
|
||||
license: MIT
|
||||
skip_upload: auto
|
||||
# Test command to verify installation
|
||||
test: |
|
||||
system "#{bin}/sshm --version"
|
||||
|
||||
# Release configuration
|
||||
release:
|
||||
github:
|
||||
owner: Gu1llaum-3
|
||||
name: sshm
|
||||
prerelease: auto
|
||||
draft: false
|
||||
replace_existing_draft: true
|
||||
target_commitish: "{{ .Commit }}"
|
||||
name_template: "{{.ProjectName}} {{.Version}}"
|
||||
header: |
|
||||
## SSHM {{.Version}}
|
||||
|
||||
Thank you for downloading SSHM!
|
||||
|
||||
### Installation
|
||||
|
||||
**Homebrew (macOS/Linux):**
|
||||
```bash
|
||||
brew tap Gu1llaum-3/sshm
|
||||
brew install sshm
|
||||
```
|
||||
|
||||
**Manual Installation:**
|
||||
Download the appropriate binary for your platform from the assets below.
|
||||
|
||||
footer: |
|
||||
## Full Changelog
|
||||
|
||||
See all changes at https://github.com/Gu1llaum-3/sshm/compare/{{.PreviousTag}}...{{.Tag}}
|
||||
|
||||
---
|
||||
|
||||
Released with ❤️ by [GoReleaser](https://github.com/goreleaser/goreleaser)
|
||||
|
||||
# Snapshot builds (for non-tag builds)
|
||||
snapshot:
|
||||
version_template: "{{ .Tag }}-snapshot-{{.ShortCommit}}"
|
||||
|
||||
# Metadata for package managers
|
||||
metadata:
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
44
Makefile
44
Makefile
@@ -1,44 +0,0 @@
|
||||
.PHONY: build build-local test clean release snapshot
|
||||
|
||||
# Version can be overridden via environment variable or command line
|
||||
VERSION ?= dev
|
||||
|
||||
# Go build flags
|
||||
LDFLAGS := -s -w -X github.com/Gu1llaum-3/sshm/cmd.AppVersion=$(VERSION)
|
||||
|
||||
# Build with specific version
|
||||
build:
|
||||
@mkdir -p dist
|
||||
go build -ldflags="$(LDFLAGS)" -o dist/sshm .
|
||||
|
||||
# Build with git version
|
||||
build-local: VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
||||
build-local: build
|
||||
|
||||
# Run tests
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
# Clean build artifacts
|
||||
clean:
|
||||
rm -rf dist
|
||||
|
||||
# Release with GoReleaser (requires tag)
|
||||
release:
|
||||
@if [ -z "$(shell git tag --points-at HEAD)" ]; then \
|
||||
echo "Error: No git tag found at current commit. Create a tag first with: git tag vX.Y.Z"; \
|
||||
exit 1; \
|
||||
fi
|
||||
goreleaser release --clean
|
||||
|
||||
# Build snapshot (without tag)
|
||||
snapshot:
|
||||
goreleaser release --snapshot --clean
|
||||
|
||||
# Check GoReleaser config
|
||||
release-check:
|
||||
goreleaser check
|
||||
|
||||
# Run GoReleaser in dry-run mode
|
||||
release-dry-run:
|
||||
goreleaser release --snapshot --skip=publish --clean
|
||||
192
README.md
192
README.md
@@ -25,39 +25,40 @@ SSHM is a beautiful command-line tool that transforms how you manage and connect
|
||||
|
||||
## ✨ Features
|
||||
|
||||
### 🚀 **Core Capabilities**
|
||||
### 🎯 **Core Features**
|
||||
- **🎨 Beautiful TUI Interface** - Navigate your SSH hosts with an elegant, interactive terminal UI
|
||||
- **⚡ 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
|
||||
- **⚡ 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
|
||||
- **🏷️ 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
|
||||
- **🔄 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
|
||||
|
||||
### 🛠️ **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
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Installation
|
||||
|
||||
**Homebrew (Recommended for macOS):**
|
||||
```bash
|
||||
brew install Gu1llaum-3/sshm/sshm
|
||||
```
|
||||
|
||||
**Unix/Linux/macOS (One-line install):**
|
||||
```bash
|
||||
curl -sSL https://raw.githubusercontent.com/Gu1llaum-3/sshm/main/install/unix.sh | bash
|
||||
@@ -104,17 +105,10 @@ 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)
|
||||
@@ -164,7 +158,6 @@ 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:**
|
||||
@@ -225,15 +218,9 @@ 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
|
||||
|
||||
@@ -249,42 +236,13 @@ sshm edit my-server
|
||||
# Edit host with custom SSH config file
|
||||
sshm edit my-server -c /path/to/custom/ssh_config
|
||||
|
||||
# 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)
|
||||
# Show version information
|
||||
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.
|
||||
@@ -299,10 +257,6 @@ 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
|
||||
@@ -323,96 +277,8 @@ 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:**
|
||||
@@ -583,29 +449,20 @@ 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
|
||||
│ │ └── 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
|
||||
│ │ └── history.go # History management and last login tracking
|
||||
│ ├── 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 with status indicators
|
||||
│ │ ├── table.go # Host list table component
|
||||
│ │ ├── 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
|
||||
@@ -633,7 +490,6 @@ 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
|
||||
|
||||
|
||||
@@ -56,25 +56,7 @@ getLatestVersion() {
|
||||
}
|
||||
|
||||
downloadBinary() {
|
||||
# Map OS names to match GoReleaser format
|
||||
local GORELEASER_OS="$OS"
|
||||
case $OS in
|
||||
"darwin") GORELEASER_OS="Darwin" ;;
|
||||
"linux") GORELEASER_OS="Linux" ;;
|
||||
"windows") GORELEASER_OS="Windows" ;;
|
||||
esac
|
||||
|
||||
# Map architecture names to match GoReleaser format
|
||||
local GORELEASER_ARCH="$ARCH"
|
||||
case $ARCH in
|
||||
"amd64") GORELEASER_ARCH="x86_64" ;;
|
||||
"arm64") GORELEASER_ARCH="arm64" ;;
|
||||
"386") GORELEASER_ARCH="i386" ;;
|
||||
"arm") GORELEASER_ARCH="armv6" ;;
|
||||
esac
|
||||
|
||||
# GoReleaser format: sshm_Darwin_arm64.tar.gz
|
||||
GITHUB_FILE="sshm_${GORELEASER_OS}_${GORELEASER_ARCH}.tar.gz"
|
||||
GITHUB_FILE="sshm-${OS}-${ARCH}.tar.gz"
|
||||
GITHUB_URL="https://github.com/Gu1llaum-3/sshm/releases/download/$LATEST_VERSION/$GITHUB_FILE"
|
||||
|
||||
printf "${YELLOW}Downloading $GITHUB_FILE...${NC}\n"
|
||||
@@ -92,8 +74,8 @@ downloadBinary() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# GoReleaser extracts the binary as just "sshm", not with the platform suffix
|
||||
EXTRACTED_BINARY="./sshm"
|
||||
# Check if the expected binary exists (no find needed)
|
||||
EXTRACTED_BINARY="./sshm-${OS}-${ARCH}"
|
||||
if [ ! -f "$EXTRACTED_BINARY" ]; then
|
||||
printf "${RED}Could not find extracted binary: $EXTRACTED_BINARY${NC}\n"
|
||||
exit 1
|
||||
|
||||
@@ -80,11 +80,7 @@ if ($LocalBinary -ne "") {
|
||||
}
|
||||
|
||||
# Download binary
|
||||
# Map architecture to match GoReleaser format
|
||||
$goreleaserArch = if ($arch -eq "amd64") { "x86_64" } else { "i386" }
|
||||
|
||||
# GoReleaser format: sshm_Windows_x86_64.zip
|
||||
$fileName = "sshm_Windows_$goreleaserArch.zip"
|
||||
$fileName = "sshm-windows-$arch.zip"
|
||||
$downloadUrl = "https://github.com/Gu1llaum-3/sshm/releases/download/$latestVersion/$fileName"
|
||||
$tempFile = "$env:TEMP\$fileName"
|
||||
|
||||
@@ -105,8 +101,7 @@ if ($LocalBinary -ne "") {
|
||||
Write-Info "Extracting..."
|
||||
try {
|
||||
Expand-Archive -Path $tempFile -DestinationPath $env:TEMP -Force
|
||||
# GoReleaser extracts the binary as just "sshm.exe", not with platform suffix
|
||||
$extractedBinary = "$env:TEMP\sshm.exe"
|
||||
$extractedBinary = "$env:TEMP\sshm-windows-$arch.exe"
|
||||
$targetPath = "$InstallDir\sshm.exe"
|
||||
|
||||
Move-Item -Path $extractedBinary -Destination $targetPath -Force
|
||||
|
||||
@@ -4,10 +4,9 @@ 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"
|
||||
@@ -149,8 +148,8 @@ func (m *addFormModel) Update(msg tea.Msg) (*addFormModel, tea.Cmd) {
|
||||
case "ctrl+c", "esc":
|
||||
return m, func() tea.Msg { return addFormCancelMsg{} }
|
||||
|
||||
case "ctrl+s":
|
||||
// Allow submission from any field with Ctrl+S (Save)
|
||||
case "ctrl+enter":
|
||||
// Allow submission from any field with Ctrl+Enter
|
||||
return m, m.submitForm()
|
||||
|
||||
case "tab", "shift+tab", "enter", "up", "down":
|
||||
@@ -239,7 +238,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+S: save • Ctrl+C/Esc: cancel"))
|
||||
b.WriteString(m.styles.FormHelp.Render("Tab/Shift+Tab: navigate • Enter on last field: submit • Ctrl+Enter: submit • Ctrl+C/Esc: cancel"))
|
||||
b.WriteString("\n")
|
||||
b.WriteString(m.styles.FormHelp.Render("* Required fields"))
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
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"
|
||||
@@ -140,8 +139,8 @@ func (m *editFormModel) Update(msg tea.Msg) (*editFormModel, tea.Cmd) {
|
||||
case "ctrl+c", "esc":
|
||||
return m, func() tea.Msg { return editFormCancelMsg{} }
|
||||
|
||||
case "ctrl+s":
|
||||
// Allow submission from any field with Ctrl+S (Save)
|
||||
case "ctrl+enter":
|
||||
// Allow submission from any field with Ctrl+Enter
|
||||
return m, m.submitEditForm()
|
||||
|
||||
case "tab", "shift+tab", "enter", "up", "down":
|
||||
@@ -248,7 +247,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+S: save • Ctrl+C/Esc: cancel"))
|
||||
b.WriteString(m.styles.FormHelp.Render("Tab/Shift+Tab: navigate • Enter on last field: submit • Ctrl+Enter: submit • Ctrl+C/Esc: cancel"))
|
||||
b.WriteString("\n")
|
||||
b.WriteString(m.styles.FormHelp.Render("* Required fields"))
|
||||
|
||||
|
||||
@@ -99,10 +99,6 @@ 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
|
||||
|
||||
@@ -42,7 +42,7 @@ func NewMoveForm(hostName string, styles Styles, width, height int, configFile s
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
return nil, fmt.Errorf("no includes found in SSH config file - move operation requires multiple config files")
|
||||
return nil, fmt.Errorf("no other config files available to move host to")
|
||||
}
|
||||
|
||||
// Create a custom file selector for move operation
|
||||
|
||||
@@ -19,7 +19,6 @@ type (
|
||||
pingResultMsg *connectivity.HostPingResult
|
||||
versionCheckMsg *version.UpdateInfo
|
||||
versionErrorMsg error
|
||||
errorMsg string
|
||||
)
|
||||
|
||||
// startPingAllCmd creates a command to ping all hosts concurrently
|
||||
@@ -158,14 +157,6 @@ 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
|
||||
@@ -596,13 +587,8 @@ 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 {
|
||||
// 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")
|
||||
}
|
||||
// Handle error - could show in UI, e.g., no other config files available
|
||||
return m, nil
|
||||
}
|
||||
m.moveForm = moveForm
|
||||
m.viewMode = ViewMove
|
||||
|
||||
@@ -72,20 +72,6 @@ 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 {
|
||||
|
||||
@@ -128,8 +128,7 @@ func TestValidateIdentityFile(t *testing.T) {
|
||||
{"empty path", "", true}, // Optional field
|
||||
{"valid file", validFile, true},
|
||||
{"non-existent file", "/path/to/nonexistent", false},
|
||||
// Skip tilde path test in CI environments where ~/.ssh/id_rsa may not exist
|
||||
// {"tilde path", "~/.ssh/id_rsa", true}, // Will pass if file exists
|
||||
{"tilde path", "~/.ssh/id_rsa", true}, // Will pass if file exists
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -139,15 +138,6 @@ func TestValidateIdentityFile(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test tilde path separately, but only if the file actually exists
|
||||
t.Run("tilde path", func(t *testing.T) {
|
||||
tildeFile := "~/.ssh/id_rsa"
|
||||
// Just test that it doesn't crash, don't assume file exists
|
||||
result := ValidateIdentityFile(tildeFile)
|
||||
// Result can be true or false depending on file existence
|
||||
_ = result // We just care that it doesn't panic
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateHost(t *testing.T) {
|
||||
@@ -184,4 +174,4 @@ func TestValidateHost(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user