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