sshman 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
- require_relative "../lib/sshman/sshman"
3
- # Check if any arguments are passed (i.e., inline commands)
2
+ require 'sshman'
3
+
4
4
  if ARGV.empty?
5
- # No arguments passed, start the interactive mode
6
- Sshman::CLI.start_interactive
5
+ SSHMan::TUI.new.start
7
6
  else
8
- # Pass command-line arguments for inline commands (e.g., `sshman list`)
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 'logger'
2
- require_relative 'constants'
3
- require_relative 'version'
4
- require_relative 'server_manager'
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 Sshman
9
- class CLI
10
- LOGGER = Logger.new(File.expand_path('~/.sshman.log'))
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
- def self.start(argv)
13
- case argv[0]
14
- when 'list'
15
- new.list_servers
16
- when 'add'
17
- new.add_server
18
- when 'edit'
19
- new.edit_server
20
- when 'delete'
21
- new.delete_server
22
- when 'connect'
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
- puts "Unknown command: #{argv[0]}. Use 'sshman help' for a list of commands."
30
+ ssh_key = nil
32
31
  end
33
- end
34
-
35
- def version
36
- puts "sshman version #{Sshman::VERSION}"
37
- end
38
32
 
39
- def self.start_interactive
40
- new.main
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
- def main
44
- ensure_csv_file
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
- loop do
47
- puts "\n#{YELLOW}Options:#{RESET_COLOR} (1)list, (2)add, (3)edit, (4)delete, (5)connect, (6)generate_key, (7)help, (q)quit"
48
- print "#{CYAN}Choose an option: #{RESET_COLOR}"
49
- option = gets.chomp.downcase.strip
50
-
51
- case option
52
- when 'list', 'ls', '1'
53
- list_servers
54
- when 'add', '2'
55
- add_server
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
- def add_server
88
- Sshman::ServerManager.add_server
55
+ Manager.update_connection(connection)
56
+ puts "Connection updated successfully!"
89
57
  end
90
58
 
91
- def edit_server
92
- Sshman::ServerManager.edit_server
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
- def delete_server
96
- Sshman::ServerManager.delete_server
97
- end
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
- def connect_to_server(alias_name = nil)
100
- Sshman::ServerManager.connect_to_server(alias_name)
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
@@ -1,5 +1,5 @@
1
1
  # lib/sshman/version.rb
2
2
 
3
- module Sshman
4
- VERSION = "0.3.3"
3
+ module SSHMan
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/sshman.rb ADDED
@@ -0,0 +1,9 @@
1
+ require_relative "sshman/version"
2
+ require_relative "sshman/cli"
3
+ require_relative "sshman/connection"
4
+ require_relative "sshman/manager"
5
+ require_relative "sshman/tui"
6
+
7
+ module SSHMan
8
+ # This is the main module for SSHMan
9
+ end
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