mirror of
https://github.com/Gu1llaum-3/sshm.git
synced 2026-01-27 03:04:21 +01:00
Compare commits
9 Commits
v1.7.0-bet
...
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 }}
|
|
||||||
136
.goreleaser.yaml
136
.goreleaser.yaml
@@ -1,136 +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 }}"
|
|
||||||
discussion_category_name: General
|
|
||||||
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
|
|
||||||
187
README.md
187
README.md
@@ -25,29 +25,35 @@ SSHM is a beautiful command-line tool that transforms how you manage and connect
|
|||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
### 🚀 **Core Capabilities**
|
### 🎯 **Core Features**
|
||||||
- **🎨 Beautiful TUI Interface** - Navigate your SSH hosts with an elegant, interactive terminal UI
|
- **🎨 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>`
|
- **⚡ Quick Connect** - Connect to any host instantly
|
||||||
- **🔄 Port Forwarding** - Easy setup for Local, Remote, and Dynamic (SOCKS) forwarding with history persistence
|
- **🔄 Port Forwarding** - Easy setup for Local, Remote, and Dynamic (SOCKS) forwarding
|
||||||
- **📝 Easy Management** - Add, edit, move, and manage SSH configurations seamlessly
|
- **📝Easy Management** - Add, edit, and manage SSH configurations seamlessly
|
||||||
- **🏷️ Tag Support** - Organize your hosts with custom tags for better categorization
|
- **🏷️ Tag Support** - Organize your hosts with custom tags for better categorization
|
||||||
- **🔍 Smart Search** - Find hosts quickly with built-in filtering and search
|
- **🔍 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
|
- **🔒 Secure** - Works directly with your existing `~/.ssh/config` file
|
||||||
- **📁 Custom Config Support** - Use any SSH configuration file with the `-c` flag
|
- **📁 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 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
|
- **⚙️ SSH Options Support** - Add any SSH configuration option through intuitive forms
|
||||||
- **🔄 Automatic Conversion** - Seamlessly converts between command-line and config formats
|
- **🔄 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
|
### 🛠️ **Management Operations**
|
||||||
- **🔗 ProxyJump Support** - Secure connection tunneling through bastion hosts
|
- **Add new SSH hosts** with interactive forms
|
||||||
- **⌨️ Keyboard Shortcuts** - Power user navigation with vim-like shortcuts
|
- **Edit existing configurations** in-place
|
||||||
- **🌐 Cross-platform** - Supports Linux, macOS (Intel & Apple Silicon), and Windows
|
- **Delete hosts** with confirmation prompts
|
||||||
- **⚡ Lightweight** - Single binary with no dependencies, zero configuration required
|
- **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
|
## 🚀 Quick Start
|
||||||
|
|
||||||
@@ -99,17 +105,10 @@ sshm
|
|||||||
- `a` - Add new host
|
- `a` - Add new host
|
||||||
- `e` - Edit selected host
|
- `e` - Edit selected host
|
||||||
- `d` - Delete selected host
|
- `d` - Delete selected host
|
||||||
- `m` - Move host to another config file (requires SSH Include directives)
|
|
||||||
- `f` - Port forwarding setup
|
- `f` - Port forwarding setup
|
||||||
- `q` - Quit
|
- `q` - Quit
|
||||||
- `/` - Search/filter hosts
|
- `/` - 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:**
|
**Sorting & Filtering:**
|
||||||
- `s` - Switch between sorting modes (name ↔ last login)
|
- `s` - Switch between sorting modes (name ↔ last login)
|
||||||
- `n` - Sort by **name** (alphabetical)
|
- `n` - Sort by **name** (alphabetical)
|
||||||
@@ -159,7 +158,6 @@ SSHM provides an intuitive interface for setting up SSH port forwarding. Press `
|
|||||||
- Configure ports and addresses with guided forms
|
- Configure ports and addresses with guided forms
|
||||||
- Optional bind address configuration (defaults to 127.0.0.1)
|
- Optional bind address configuration (defaults to 127.0.0.1)
|
||||||
- Real-time validation of port numbers and addresses
|
- Real-time validation of port numbers and addresses
|
||||||
- **Port forwarding history** - Save frequently used configurations for quick reuse
|
|
||||||
- Connect automatically with configured forwarding options
|
- Connect automatically with configured forwarding options
|
||||||
|
|
||||||
**Troubleshooting Port Forwarding:**
|
**Troubleshooting Port Forwarding:**
|
||||||
@@ -220,15 +218,9 @@ SSHM provides both command-line operations and an interactive TUI interface:
|
|||||||
# Launch interactive TUI mode for browsing and connecting to hosts
|
# Launch interactive TUI mode for browsing and connecting to hosts
|
||||||
sshm
|
sshm
|
||||||
|
|
||||||
# Connect directly to a specific host (with history tracking)
|
|
||||||
sshm my-server
|
|
||||||
|
|
||||||
# Launch TUI with custom SSH config file
|
# Launch TUI with custom SSH config file
|
||||||
sshm -c /path/to/custom/ssh_config
|
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
|
# Add a new host using interactive form
|
||||||
sshm add
|
sshm add
|
||||||
|
|
||||||
@@ -244,42 +236,13 @@ sshm edit my-server
|
|||||||
# Edit host with custom SSH config file
|
# Edit host with custom SSH config file
|
||||||
sshm edit my-server -c /path/to/custom/ssh_config
|
sshm edit my-server -c /path/to/custom/ssh_config
|
||||||
|
|
||||||
# Move a host to another SSH config file (requires Include directives)
|
# Show version information
|
||||||
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
|
sshm --version
|
||||||
|
|
||||||
# Show help and available commands
|
# Show help and available commands
|
||||||
sshm --help
|
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
|
### Backup Configuration
|
||||||
|
|
||||||
SSHM automatically creates backups of your SSH configuration files before making any changes to ensure your configurations are safe.
|
SSHM automatically creates backups of your SSH configuration files before making any changes to ensure your configurations are safe.
|
||||||
@@ -294,10 +257,6 @@ SSHM automatically creates backups of your SSH configuration files before making
|
|||||||
- Stored separately to avoid SSH Include conflicts
|
- Stored separately to avoid SSH Include conflicts
|
||||||
- Easy manual recovery if needed
|
- 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:**
|
**Quick Recovery:**
|
||||||
```bash
|
```bash
|
||||||
# Unix/Linux/macOS
|
# Unix/Linux/macOS
|
||||||
@@ -318,96 +277,8 @@ sshm -c /path/to/custom/ssh_config
|
|||||||
# Use custom config file with commands
|
# Use custom config file with commands
|
||||||
sshm add hostname -c /path/to/custom/ssh_config
|
sshm add hostname -c /path/to/custom/ssh_config
|
||||||
sshm edit 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
|
### Platform-Specific Notes
|
||||||
|
|
||||||
**Windows:**
|
**Windows:**
|
||||||
@@ -578,29 +449,20 @@ sshm/
|
|||||||
│ ├── root.go # Root command and interactive mode
|
│ ├── root.go # Root command and interactive mode
|
||||||
│ ├── add.go # Add host command
|
│ ├── add.go # Add host command
|
||||||
│ ├── edit.go # Edit host command
|
│ ├── edit.go # Edit host command
|
||||||
│ ├── move.go # Move host command
|
|
||||||
│ └── search.go # Search command
|
│ └── search.go # Search command
|
||||||
├── internal/
|
├── internal/
|
||||||
│ ├── config/ # SSH configuration management
|
│ ├── config/ # SSH configuration management
|
||||||
│ │ └── ssh.go # Config parsing and manipulation
|
│ │ └── ssh.go # Config parsing and manipulation
|
||||||
│ ├── connectivity/ # SSH connectivity checking
|
|
||||||
│ │ └── ping.go # Asynchronous SSH ping functionality
|
|
||||||
│ ├── history/ # Connection history tracking
|
│ ├── 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)
|
│ ├── ui/ # Terminal UI components (Bubble Tea)
|
||||||
│ │ ├── tui.go # Main TUI interface and program setup
|
│ │ ├── tui.go # Main TUI interface and program setup
|
||||||
│ │ ├── model.go # Core TUI model and state
|
│ │ ├── model.go # Core TUI model and state
|
||||||
│ │ ├── update.go # Message handling and state updates
|
│ │ ├── update.go # Message handling and state updates
|
||||||
│ │ ├── view.go # UI rendering and layout
|
│ │ ├── 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
|
│ │ ├── add_form.go # Add host form interface
|
||||||
│ │ ├── edit_form.go# Edit 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
|
│ │ ├── styles.go # Lip Gloss styling definitions
|
||||||
│ │ ├── sort.go # Sorting and filtering logic
|
│ │ ├── sort.go # Sorting and filtering logic
|
||||||
│ │ └── utils.go # UI utility functions
|
│ │ └── utils.go # UI utility functions
|
||||||
@@ -628,7 +490,6 @@ sshm/
|
|||||||
- [Bubble Tea](https://github.com/charmbracelet/bubbletea) - TUI framework
|
- [Bubble Tea](https://github.com/charmbracelet/bubbletea) - TUI framework
|
||||||
- [Bubbles](https://github.com/charmbracelet/bubbles) - TUI components
|
- [Bubbles](https://github.com/charmbracelet/bubbles) - TUI components
|
||||||
- [Lipgloss](https://github.com/charmbracelet/lipgloss) - Styling
|
- [Lipgloss](https://github.com/charmbracelet/lipgloss) - Styling
|
||||||
- [Go Crypto SSH](https://golang.org/x/crypto/ssh) - SSH connectivity checking
|
|
||||||
|
|
||||||
## 📦 Releases
|
## 📦 Releases
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Gu1llaum-3/sshm/internal/config"
|
"github.com/Gu1llaum-3/sshm/internal/config"
|
||||||
"github.com/Gu1llaum-3/sshm/internal/validation"
|
"github.com/Gu1llaum-3/sshm/internal/validation"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
@@ -149,8 +148,8 @@ func (m *addFormModel) Update(msg tea.Msg) (*addFormModel, tea.Cmd) {
|
|||||||
case "ctrl+c", "esc":
|
case "ctrl+c", "esc":
|
||||||
return m, func() tea.Msg { return addFormCancelMsg{} }
|
return m, func() tea.Msg { return addFormCancelMsg{} }
|
||||||
|
|
||||||
case "ctrl+s":
|
case "ctrl+enter":
|
||||||
// Allow submission from any field with Ctrl+S (Save)
|
// Allow submission from any field with Ctrl+Enter
|
||||||
return m, m.submitForm()
|
return m, m.submitForm()
|
||||||
|
|
||||||
case "tab", "shift+tab", "enter", "up", "down":
|
case "tab", "shift+tab", "enter", "up", "down":
|
||||||
@@ -239,7 +238,7 @@ func (m *addFormModel) View() string {
|
|||||||
b.WriteString("\n\n")
|
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("\n")
|
||||||
b.WriteString(m.styles.FormHelp.Render("* Required fields"))
|
b.WriteString(m.styles.FormHelp.Render("* Required fields"))
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Gu1llaum-3/sshm/internal/config"
|
"github.com/Gu1llaum-3/sshm/internal/config"
|
||||||
"github.com/Gu1llaum-3/sshm/internal/validation"
|
"github.com/Gu1llaum-3/sshm/internal/validation"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
@@ -140,8 +139,8 @@ func (m *editFormModel) Update(msg tea.Msg) (*editFormModel, tea.Cmd) {
|
|||||||
case "ctrl+c", "esc":
|
case "ctrl+c", "esc":
|
||||||
return m, func() tea.Msg { return editFormCancelMsg{} }
|
return m, func() tea.Msg { return editFormCancelMsg{} }
|
||||||
|
|
||||||
case "ctrl+s":
|
case "ctrl+enter":
|
||||||
// Allow submission from any field with Ctrl+S (Save)
|
// Allow submission from any field with Ctrl+Enter
|
||||||
return m, m.submitEditForm()
|
return m, m.submitEditForm()
|
||||||
|
|
||||||
case "tab", "shift+tab", "enter", "up", "down":
|
case "tab", "shift+tab", "enter", "up", "down":
|
||||||
@@ -248,7 +247,7 @@ func (m *editFormModel) View() string {
|
|||||||
b.WriteString("\n\n")
|
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("\n")
|
||||||
b.WriteString(m.styles.FormHelp.Render("* Required fields"))
|
b.WriteString(m.styles.FormHelp.Render("* Required fields"))
|
||||||
|
|
||||||
|
|||||||
@@ -99,10 +99,6 @@ type Model struct {
|
|||||||
height int
|
height int
|
||||||
styles Styles
|
styles Styles
|
||||||
ready bool
|
ready bool
|
||||||
|
|
||||||
// Error handling
|
|
||||||
errorMessage string
|
|
||||||
showingError bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateTableStyles updates the table header border color based on focus state
|
// 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 {
|
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
|
// Create a custom file selector for move operation
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ type (
|
|||||||
pingResultMsg *connectivity.HostPingResult
|
pingResultMsg *connectivity.HostPingResult
|
||||||
versionCheckMsg *version.UpdateInfo
|
versionCheckMsg *version.UpdateInfo
|
||||||
versionErrorMsg error
|
versionErrorMsg error
|
||||||
errorMsg string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// startPingAllCmd creates a command to ping all hosts concurrently
|
// 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
|
// as it might disrupt the user experience
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
case errorMsg:
|
|
||||||
// Handle general error messages
|
|
||||||
if string(msg) == "clear" {
|
|
||||||
m.showingError = false
|
|
||||||
m.errorMessage = ""
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
|
|
||||||
case addFormSubmitMsg:
|
case addFormSubmitMsg:
|
||||||
if msg.err != nil {
|
if msg.err != nil {
|
||||||
// Show error in form
|
// 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
|
hostName := extractHostNameFromTableRow(selected[0]) // Extract hostname from first column
|
||||||
moveForm, err := NewMoveForm(hostName, m.styles, m.width, m.height, m.configFile)
|
moveForm, err := NewMoveForm(hostName, m.styles, m.width, m.height, m.configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Show error message to user
|
// Handle error - could show in UI, e.g., no other config files available
|
||||||
m.errorMessage = err.Error()
|
return m, nil
|
||||||
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.moveForm = moveForm
|
||||||
m.viewMode = ViewMove
|
m.viewMode = ViewMove
|
||||||
|
|||||||
@@ -72,20 +72,6 @@ func (m Model) renderListView() string {
|
|||||||
components = append(components, updateStyle.Render(updateText))
|
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
|
// Add the search bar with the appropriate style based on focus
|
||||||
searchPrompt := "Search (/ to focus): "
|
searchPrompt := "Search (/ to focus): "
|
||||||
if m.searchMode {
|
if m.searchMode {
|
||||||
|
|||||||
@@ -128,8 +128,7 @@ func TestValidateIdentityFile(t *testing.T) {
|
|||||||
{"empty path", "", true}, // Optional field
|
{"empty path", "", true}, // Optional field
|
||||||
{"valid file", validFile, true},
|
{"valid file", validFile, true},
|
||||||
{"non-existent file", "/path/to/nonexistent", false},
|
{"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 {
|
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) {
|
func TestValidateHost(t *testing.T) {
|
||||||
@@ -184,4 +174,4 @@ func TestValidateHost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user