sshman 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitattributes +2 -0
- data/.gitignore +71 -0
- data/Gemfile +8 -0
- data/LICENSE +674 -0
- data/README.md +93 -0
- data/exe/sshman +4 -6
- data/images/sshman.gif +0 -0
- data/lib/sshman/cli.rb +62 -85
- data/lib/sshman/connection.rb +30 -0
- data/lib/sshman/manager.rb +52 -0
- data/lib/sshman/tui.rb +136 -0
- data/lib/sshman/version.rb +2 -2
- data/lib/sshman.rb +9 -0
- data/sshman.gemspec +31 -0
- metadata +106 -19
- data/lib/sshman/constants.rb +0 -8
- data/lib/sshman/key_manager.rb +0 -38
- data/lib/sshman/server_manager.rb +0 -131
- data/lib/sshman/sshman.rb +0 -4
- data/lib/sshman/utils.rb +0 -28
data/README.md
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
[![Gem Version](https://img.shields.io/gem/v/sshman)](https://rubygems.org/gems/sshman)
|
2
|
+
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
|
3
|
+
# 🚀 sshman: A Simple and Secure SSH Manager
|
4
|
+
![sshmangif](images/sshman.gif)
|
5
|
+
**Sshman** is a *terminal-based* SSH manager built with Ruby that simplifies managing your SSH connections. It enables you to easily add, edit, delete, and connect to servers with just a few commands. The tool supports SSH keys and passwords and secures your configuration with restricted file permissions.
|
6
|
+
|
7
|
+
## ✨ Features
|
8
|
+
|
9
|
+
- Add SSH server configurations
|
10
|
+
- List all saved servers in a neatly formatted table
|
11
|
+
- Edit existing server configurations
|
12
|
+
- Delete server entries by alias
|
13
|
+
- Connect to servers via SSH in a single command
|
14
|
+
- Security: Restricts file permissions to protect SSH keys
|
15
|
+
- User-friendly CLI: Interactive, colorized terminal-based interface with built-in help
|
16
|
+
- SSH-key generation: Easy fool proof ssh-key generation
|
17
|
+
|
18
|
+
## 📦 Installation
|
19
|
+
|
20
|
+
### Using RubyGems
|
21
|
+
Install the gem locally or globally:
|
22
|
+
|
23
|
+
```shell
|
24
|
+
gem install sshman
|
25
|
+
```
|
26
|
+
###From Source
|
27
|
+
|
28
|
+
Clone the repository:
|
29
|
+
|
30
|
+
```shell
|
31
|
+
git clone https://github.com/subnetmasked/sshman.git
|
32
|
+
cd sshman
|
33
|
+
```
|
34
|
+
Build and install the gem:
|
35
|
+
|
36
|
+
```shell
|
37
|
+
gem build sshman.gemspec
|
38
|
+
gem install ./sshman-X.X.X.gem --user-install
|
39
|
+
```
|
40
|
+
## 🚀 Quick Start
|
41
|
+
After installing the gem, you can use the sshman command to manage your SSH servers.
|
42
|
+
|
43
|
+
Inline Commands
|
44
|
+
You can now run commands directly:
|
45
|
+
|
46
|
+
- **sshman list** - List all saved servers
|
47
|
+
- **shman add** - Add a new server configuration
|
48
|
+
- **sshman edit** - Edit an existing server
|
49
|
+
- **sshman delete** - Delete a server by its alias
|
50
|
+
- **sshman connect** - Connect to a server by its alias
|
51
|
+
- **sshman help** - Display help information
|
52
|
+
|
53
|
+
## Interactive Mode
|
54
|
+
Simply run:
|
55
|
+
```shell
|
56
|
+
sshman
|
57
|
+
```
|
58
|
+
|
59
|
+
This will launch the interactive menu where you can add, edit, delete, or connect to servers interactively.
|
60
|
+
|
61
|
+
# sshman Roadmap
|
62
|
+
|
63
|
+
- Add SSH server configurations✅
|
64
|
+
- List all saved servers in a neatly formatted table✅
|
65
|
+
- Edit existing server configurations✅
|
66
|
+
- Delete server entries by alias✅
|
67
|
+
- Connect to servers via SSH in a single command✅
|
68
|
+
- Security: Restricts file permissions to protect SSH keys✅
|
69
|
+
- User-friendly CLI: Interactive, colorized terminal-based interface with built-in help✅
|
70
|
+
- Session logging and replay✅
|
71
|
+
- SSH key generation✅
|
72
|
+
- Remote script execution⏳
|
73
|
+
- Multi-factor authentication (MFA) support⏳
|
74
|
+
- Automated server health checks⏳
|
75
|
+
- Dashboard for server status⏳
|
76
|
+
- Customizable connection profiles⏳
|
77
|
+
- Import/export server configurations⏳
|
78
|
+
- Batch operations on multiple servers⏳
|
79
|
+
|
80
|
+
## 🤝 Contributing
|
81
|
+
Contributions, issues, and feature requests are welcome! Feel free to check the issues page to report bugs or request features.
|
82
|
+
|
83
|
+
- Fork the project
|
84
|
+
- Create your feature branch: git checkout -b my-new-feature
|
85
|
+
- Commit your changes: git commit -m 'Add some feature'
|
86
|
+
- Push to the branch: git push origin my-new-feature
|
87
|
+
- Submit a pull request
|
88
|
+
|
89
|
+
## 📄 License
|
90
|
+
This project is licensed under the GNU General Public License (GPL-3.0) - see the LICENSE file for details.
|
91
|
+
|
92
|
+
## 💬 Questions?
|
93
|
+
If you have any questions or need further clarification, feel free to open an issue or reach out to me at subnetmasked@cock.li.
|
data/exe/sshman
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
|
2
|
+
require 'sshman'
|
3
|
+
|
4
4
|
if ARGV.empty?
|
5
|
-
|
6
|
-
Sshman::CLI.start_interactive
|
5
|
+
SSHMan::TUI.new.start
|
7
6
|
else
|
8
|
-
|
9
|
-
Sshman::CLI.start(ARGV)
|
7
|
+
SSHMan::CLI.start(ARGV)
|
10
8
|
end
|
data/images/sshman.gif
ADDED
Binary file
|
data/lib/sshman/cli.rb
CHANGED
@@ -1,103 +1,80 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
require_relative '
|
5
|
-
require_relative 'utils'
|
6
|
-
require_relative 'key_manager'
|
1
|
+
require 'thor'
|
2
|
+
require 'tty-table'
|
3
|
+
require 'tty-prompt'
|
4
|
+
require_relative 'manager'
|
7
5
|
|
8
|
-
module
|
9
|
-
class CLI
|
10
|
-
|
6
|
+
module SSHMan
|
7
|
+
class CLI < Thor
|
8
|
+
desc "list", "List all SSH connections"
|
9
|
+
def list
|
10
|
+
connections = Manager.list_connections
|
11
|
+
table = TTY::Table.new(
|
12
|
+
header: ['Name', 'Host', 'User', 'Port', 'Auth Type'],
|
13
|
+
rows: connections.map { |c| [c.name, c.host, c.user, c.port, c.auth_type] }
|
14
|
+
)
|
15
|
+
puts table.render(:unicode, padding: [0, 1])
|
16
|
+
end
|
11
17
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
new.connect_to_server(argv[1])
|
24
|
-
when 'generate_key'
|
25
|
-
KeyManager.generate_key
|
26
|
-
when 'help'
|
27
|
-
new.display_help
|
28
|
-
when 'version'
|
29
|
-
new.version
|
18
|
+
desc "add", "Add a new SSH connection"
|
19
|
+
def add
|
20
|
+
prompt = TTY::Prompt.new
|
21
|
+
name = prompt.ask("Enter connection name:")
|
22
|
+
host = prompt.ask("Enter host:")
|
23
|
+
user = prompt.ask("Enter username:")
|
24
|
+
port = prompt.ask("Enter port:", default: "22")
|
25
|
+
auth_type = prompt.select("Choose authentication type:", %w(Password SSH\ Key))
|
26
|
+
|
27
|
+
if auth_type == "SSH Key"
|
28
|
+
ssh_key = prompt.ask("Enter path to SSH key (relative to ~/.ssh/):")
|
30
29
|
else
|
31
|
-
|
30
|
+
ssh_key = nil
|
32
31
|
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def version
|
36
|
-
puts "sshman version #{Sshman::VERSION}"
|
37
|
-
end
|
38
32
|
|
39
|
-
|
40
|
-
|
33
|
+
connection = Connection.new(name, host, user, port, auth_type, ssh_key)
|
34
|
+
Manager.add_connection(connection)
|
35
|
+
puts "Connection added successfully!"
|
41
36
|
end
|
42
37
|
|
43
|
-
|
44
|
-
|
38
|
+
desc "edit NAME", "Edit an existing SSH connection"
|
39
|
+
def edit(name)
|
40
|
+
connection = Manager.find_connection(name)
|
41
|
+
return puts "Connection not found." unless connection
|
45
42
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
when 'edit', '3'
|
57
|
-
edit_server
|
58
|
-
when 'delete', '4'
|
59
|
-
delete_server
|
60
|
-
when 'connect', '5'
|
61
|
-
connect_to_server
|
62
|
-
when 'generate_key', '6'
|
63
|
-
KeyManager.generate_key
|
64
|
-
when 'help', '7'
|
65
|
-
display_help
|
66
|
-
when 'quit', 'q'
|
67
|
-
puts "#{GREEN}Goodbye!#{RESET_COLOR}"
|
68
|
-
break
|
69
|
-
else
|
70
|
-
puts "#{RED}Unknown option '#{option}'. Type 'help' for usage instructions.#{RESET_COLOR}"
|
71
|
-
end
|
43
|
+
prompt = TTY::Prompt.new
|
44
|
+
connection.host = prompt.ask("Enter new host:", default: connection.host)
|
45
|
+
connection.user = prompt.ask("Enter new username:", default: connection.user)
|
46
|
+
connection.port = prompt.ask("Enter new port:", default: connection.port)
|
47
|
+
connection.auth_type = prompt.select("Choose new authentication type:", %w(Password SSH\ Key), default: connection.auth_type)
|
48
|
+
|
49
|
+
if connection.auth_type == "SSH Key"
|
50
|
+
connection.ssh_key = prompt.ask("Enter new path to SSH key (relative to ~/.ssh/):", default: connection.ssh_key)
|
51
|
+
else
|
52
|
+
connection.ssh_key = nil
|
72
53
|
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def ensure_csv_file
|
76
|
-
Sshman::Utils.ensure_csv_file
|
77
|
-
end
|
78
|
-
|
79
|
-
def display_help
|
80
|
-
Sshman::Utils.display_help
|
81
|
-
end
|
82
|
-
|
83
|
-
def list_servers
|
84
|
-
Sshman::ServerManager.list_servers
|
85
|
-
end
|
86
54
|
|
87
|
-
|
88
|
-
|
55
|
+
Manager.update_connection(connection)
|
56
|
+
puts "Connection updated successfully!"
|
89
57
|
end
|
90
58
|
|
91
|
-
|
92
|
-
|
59
|
+
desc "delete NAME", "Delete an SSH connection"
|
60
|
+
def delete(name)
|
61
|
+
if Manager.delete_connection(name)
|
62
|
+
puts "Connection deleted successfully!"
|
63
|
+
else
|
64
|
+
puts "Connection not found."
|
65
|
+
end
|
93
66
|
end
|
94
67
|
|
95
|
-
|
96
|
-
|
97
|
-
|
68
|
+
desc "connect NAME", "Connect to a saved SSH connection"
|
69
|
+
def connect(name)
|
70
|
+
connection = Manager.find_connection(name)
|
71
|
+
return puts "Connection not found." unless connection
|
98
72
|
|
99
|
-
|
100
|
-
|
73
|
+
cmd = "ssh #{connection.user}@#{connection.host} -p #{connection.port}"
|
74
|
+
cmd += " -i ~/.ssh/#{connection.ssh_key}" if connection.auth_type == "SSH Key"
|
75
|
+
|
76
|
+
puts "Connecting to #{connection.name}..."
|
77
|
+
exec(cmd)
|
101
78
|
end
|
102
79
|
end
|
103
80
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# lib/sshman/connection.rb
|
2
|
+
module SSHMan
|
3
|
+
class Connection
|
4
|
+
attr_accessor :name, :host, :user, :port, :auth_type, :ssh_key
|
5
|
+
|
6
|
+
def initialize(name, host, user, port, auth_type, ssh_key = nil)
|
7
|
+
@name = name
|
8
|
+
@host = host
|
9
|
+
@user = user
|
10
|
+
@port = port
|
11
|
+
@auth_type = auth_type
|
12
|
+
@ssh_key = ssh_key
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
{
|
17
|
+
name: @name,
|
18
|
+
host: @host,
|
19
|
+
user: @user,
|
20
|
+
port: @port,
|
21
|
+
auth_type: @auth_type,
|
22
|
+
ssh_key: @ssh_key
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.from_h(hash)
|
27
|
+
new(hash[:name], hash[:host], hash[:user], hash[:port], hash[:auth_type], hash[:ssh_key])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# lib/sshman/manager.rb
|
2
|
+
require 'yaml'
|
3
|
+
require_relative 'connection'
|
4
|
+
|
5
|
+
module SSHMan
|
6
|
+
class Manager
|
7
|
+
CONFIG_FILE = File.expand_path('~/.sshman.yml')
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def list_connections
|
11
|
+
load_connections
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_connection(connection)
|
15
|
+
connections = load_connections
|
16
|
+
connections << connection
|
17
|
+
save_connections(connections)
|
18
|
+
end
|
19
|
+
|
20
|
+
def update_connection(updated_connection)
|
21
|
+
connections = load_connections
|
22
|
+
index = connections.index { |c| c.name == updated_connection.name }
|
23
|
+
connections[index] = updated_connection if index
|
24
|
+
save_connections(connections)
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete_connection(name)
|
28
|
+
connections = load_connections
|
29
|
+
initial_count = connections.count
|
30
|
+
connections.reject! { |c| c.name == name }
|
31
|
+
save_connections(connections)
|
32
|
+
initial_count != connections.count
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_connection(name)
|
36
|
+
load_connections.find { |c| c.name == name }
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def load_connections
|
42
|
+
return [] unless File.exist?(CONFIG_FILE)
|
43
|
+
|
44
|
+
YAML.load_file(CONFIG_FILE).map { |hash| Connection.from_h(hash) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def save_connections(connections)
|
48
|
+
File.write(CONFIG_FILE, connections.map(&:to_h).to_yaml)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/sshman/tui.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# lib/sshman/tui.rb
|
2
|
+
require 'tty-prompt'
|
3
|
+
require_relative 'manager'
|
4
|
+
require_relative 'connection'
|
5
|
+
|
6
|
+
module SSHMan
|
7
|
+
class TUI
|
8
|
+
def initialize
|
9
|
+
@prompt = TTY::Prompt.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def start
|
13
|
+
loop do
|
14
|
+
case main_menu
|
15
|
+
when 'List Connections'
|
16
|
+
list_connections
|
17
|
+
when 'Add Connection'
|
18
|
+
add_connection
|
19
|
+
when 'Edit Connection'
|
20
|
+
edit_connection
|
21
|
+
when 'Delete Connection'
|
22
|
+
delete_connection
|
23
|
+
when 'Connect'
|
24
|
+
connect
|
25
|
+
when 'Exit'
|
26
|
+
break
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def main_menu
|
34
|
+
@prompt.select("SSHMan - Main Menu") do |menu|
|
35
|
+
menu.choice 'List Connections'
|
36
|
+
menu.choice 'Add Connection'
|
37
|
+
menu.choice 'Edit Connection'
|
38
|
+
menu.choice 'Delete Connection'
|
39
|
+
menu.choice 'Connect'
|
40
|
+
menu.choice 'Exit'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def list_connections
|
45
|
+
connections = Manager.list_connections
|
46
|
+
if connections.empty?
|
47
|
+
puts "No connections found."
|
48
|
+
else
|
49
|
+
table = TTY::Table.new(
|
50
|
+
header: ['Name', 'Host', 'User', 'Port', 'Auth Type'],
|
51
|
+
rows: connections.map { |c| [c.name, c.host, c.user, c.port, c.auth_type] }
|
52
|
+
)
|
53
|
+
puts table.render(:unicode, padding: [0, 1])
|
54
|
+
end
|
55
|
+
@prompt.keypress("Press any key to continue")
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_connection
|
59
|
+
name = @prompt.ask("Enter connection name:")
|
60
|
+
host = @prompt.ask("Enter host:")
|
61
|
+
user = @prompt.ask("Enter username:")
|
62
|
+
port = @prompt.ask("Enter port:", default: "22")
|
63
|
+
auth_type = @prompt.select("Choose authentication type:", %w(Password SSH\ Key))
|
64
|
+
|
65
|
+
if auth_type == "SSH Key"
|
66
|
+
ssh_key = @prompt.ask("Enter path to SSH key (relative to ~/.ssh/):")
|
67
|
+
else
|
68
|
+
ssh_key = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
connection = Connection.new(name, host, user, port, auth_type, ssh_key)
|
72
|
+
Manager.add_connection(connection)
|
73
|
+
puts "Connection added successfully!"
|
74
|
+
@prompt.keypress("Press any key to continue")
|
75
|
+
end
|
76
|
+
|
77
|
+
def edit_connection
|
78
|
+
connections = Manager.list_connections
|
79
|
+
if connections.empty?
|
80
|
+
puts "No connections found."
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
name = @prompt.select("Select connection to edit:", connections.map(&:name))
|
85
|
+
connection = Manager.find_connection(name)
|
86
|
+
|
87
|
+
connection.host = @prompt.ask("Enter new host:", default: connection.host)
|
88
|
+
connection.user = @prompt.ask("Enter new username:", default: connection.user)
|
89
|
+
connection.port = @prompt.ask("Enter new port:", default: connection.port)
|
90
|
+
connection.auth_type = @prompt.select("Choose new authentication type:", %w(Password SSH\ Key), default: connection.auth_type)
|
91
|
+
|
92
|
+
if connection.auth_type == "SSH Key"
|
93
|
+
connection.ssh_key = @prompt.ask("Enter new path to SSH key (relative to ~/.ssh/):", default: connection.ssh_key)
|
94
|
+
else
|
95
|
+
connection.ssh_key = nil
|
96
|
+
end
|
97
|
+
|
98
|
+
Manager.update_connection(connection)
|
99
|
+
puts "Connection updated successfully!"
|
100
|
+
@prompt.keypress("Press any key to continue")
|
101
|
+
end
|
102
|
+
|
103
|
+
def delete_connection
|
104
|
+
connections = Manager.list_connections
|
105
|
+
if connections.empty?
|
106
|
+
puts "No connections found."
|
107
|
+
return
|
108
|
+
end
|
109
|
+
|
110
|
+
name = @prompt.select("Select connection to delete:", connections.map(&:name))
|
111
|
+
if Manager.delete_connection(name)
|
112
|
+
puts "Connection deleted successfully!"
|
113
|
+
else
|
114
|
+
puts "Failed to delete connection."
|
115
|
+
end
|
116
|
+
@prompt.keypress("Press any key to continue")
|
117
|
+
end
|
118
|
+
|
119
|
+
def connect
|
120
|
+
connections = Manager.list_connections
|
121
|
+
if connections.empty?
|
122
|
+
puts "No connections found."
|
123
|
+
return
|
124
|
+
end
|
125
|
+
|
126
|
+
name = @prompt.select("Select connection to connect:", connections.map(&:name))
|
127
|
+
connection = Manager.find_connection(name)
|
128
|
+
|
129
|
+
cmd = "ssh #{connection.user}@#{connection.host} -p #{connection.port}"
|
130
|
+
cmd += " -i ~/.ssh/#{connection.ssh_key}" if connection.auth_type == "SSH Key"
|
131
|
+
|
132
|
+
puts "Connecting to #{connection.name}..."
|
133
|
+
system(cmd)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/lib/sshman/version.rb
CHANGED
data/lib/sshman.rb
ADDED
data/sshman.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# sshman.gemspec
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "sshman/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sshman"
|
8
|
+
spec.version = SSHMan::VERSION
|
9
|
+
spec.authors = ["Subnetmasked"]
|
10
|
+
spec.email = ["subnetmasked@cock.li"]
|
11
|
+
|
12
|
+
spec.summary = %q{A fancy SSH connection manager with TUI}
|
13
|
+
spec.description = %q{SSHMan is a command-line tool for managing SSH connections with a user-friendly interface and a Text-based User Interface (TUI).}
|
14
|
+
spec.homepage = "https://github.com/subnetmasked/sshman"
|
15
|
+
spec.license = "GPL-3.0-OR-LATER"
|
16
|
+
|
17
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_dependency "thor", "~> 1.2"
|
25
|
+
spec.add_dependency "tty-table", "~> 0.12"
|
26
|
+
spec.add_dependency "tty-prompt", "~> 0.23"
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
29
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
30
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
31
|
+
end
|