sshman 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](https://rubygems.org/gems/sshman)
|
2
|
+
[](https://www.gnu.org/licenses/gpl-3.0)
|
3
|
+
# 🚀 sshman: A Simple and Secure SSH Manager
|
4
|
+

|
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
|