incline 0.2.3 → 0.2.4

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.
@@ -0,0 +1,62 @@
1
+ require 'securerandom'
2
+
3
+ module Incline
4
+ class CLI
5
+ class Prepare
6
+
7
+ FLY_TRAP_PING = "ft-" + SecureRandom.urlsafe_base64(9)
8
+
9
+ FLY_TRAP_ROUTES = <<-EOCFG
10
+ Rails.application.routes.draw do
11
+ # [#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}]
12
+ # This route defines the ping address, which is a unique address generated specifically for this web server.
13
+ # No other web server or host knows it.
14
+ # If you decide to change this address, you will want to update the associated crontab job as well.
15
+ get '/#{FLY_TRAP_PING}', controller: 'trap', action: 'ping'
16
+
17
+ # These two simply pour everything else into the trap controller for logging.
18
+ get '/(:trigger)', trigger: /.+/, controller: 'trap', action: 'index'
19
+ root 'trap#index'
20
+ end
21
+ EOCFG
22
+
23
+ FLY_TRAP_SECRETS = <<-EOCFG
24
+ # [#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}]
25
+ # The secrets were generated specifically for this application installation.
26
+ test:
27
+ secret_key_base: #{SecureRandom.urlsafe_base64(60)}
28
+ development:
29
+ secret_key_base: #{SecureRandom.urlsafe_base64(60)}
30
+ production:
31
+ secret_key_base: #{SecureRandom.urlsafe_base64(60)}
32
+ EOCFG
33
+
34
+ private_constant :FLY_TRAP_PING, :FLY_TRAP_ROUTES, :FLY_TRAP_SECRETS
35
+
36
+ def flytrap_path
37
+ "http://#{@options[:host]}/#{FLY_TRAP_PING}"
38
+ end
39
+
40
+ private
41
+
42
+ def install_flytrap(shell)
43
+ shell.with_stat('Installing flytrap') do
44
+ shell.exec "if [ ! -d #{shell.home_path}/apps ]; then mkdir #{shell.home_path}/apps; fi"
45
+ shell.exec "chmod 775 #{shell.home_path}/apps"
46
+ # install the fly_trap app and write the new routes.rb file.
47
+ shell.exec "git clone https://github.com/barkerest/fly_trap.git #{shell.home_path}/apps/fly_trap"
48
+ shell.write_file "#{shell.home_path}/apps/fly_trap/config/routes.rb", FLY_TRAP_ROUTES
49
+ shell.write_file "#{shell.home_path}/apps/fly_trap/config/secrets.yml", FLY_TRAP_SECRETS
50
+ # prep the app.
51
+ shell.exec "cd #{shell.home_path}/apps/fly_trap"
52
+ shell.exec "bundle install --deployment"
53
+ shell.exec "bundle exec rake db:migrate:reset RAILS_ENV=production"
54
+ shell.exec "bundle exec rake assets:precompile RAILS_ENV=production RAILS_GROUPS=assets RAILS_RELATIVE_URL_ROOT=\"/\""
55
+ shell.exec "cd #{shell.home_path}"
56
+ # generate the cron job
57
+ shell.exec "(crontab -l; echo \"*/5 * * * * curl http://localhost/#{FLY_TRAP_PING} >/dev/null 2>&1\";) | crontab -"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,37 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+
6
+ private
7
+
8
+ def install_passenger(shell)
9
+ distros = {
10
+ '12.04' => 'precise',
11
+ '12.10' => 'quantal',
12
+ '13.04' => 'raring',
13
+ '13.10' => 'saucy',
14
+ '14.04' => 'trusty',
15
+ '14.10' => 'utopic',
16
+ '15.04' => 'vivid',
17
+ '15.10' => 'wily',
18
+ '16.04' => 'xenial',
19
+ '16.10' => 'yakkety',
20
+ '17.04' => 'zesty'
21
+ }
22
+
23
+ distro = distros[host_info['VERSION_ID']]
24
+ shell.with_stat('Installing Phusion Passenger') do
25
+ shell.sudo_exec 'apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7'
26
+ shell.apt_get 'install apt-transport-https ca-certificates'
27
+ shell.sudo_exec "echo deb https://oss-binaries.phusionpassenger.com/apt/passenger #{distro} main > /etc/apt/sources.list.d/passenger.list"
28
+ shell.apt_get 'update'
29
+ shell.apt_get 'install nginx-extras passenger'
30
+ shell.sudo_exec_ignore_code 'systemctl stop nginx'
31
+ shell.sudo_exec 'systemctl start nginx'
32
+ shell.sudo_exec 'systemctl enable nginx'
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+ private
6
+
7
+ def install_prereqs(shell)
8
+ shell.with_stat('Installing prerequisites') do
9
+ shell.apt_get 'install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties libffi-dev'
10
+ end
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Incline
2
+ class CLI
3
+ class Prepare
4
+
5
+ private
6
+
7
+ def install_rails(shell)
8
+ shell.with_stat("Installing Rails #{@options[:rails_version]}") do
9
+ shell.exec "gem install rails -v #{@options[:rails_version]}"
10
+ shell.exec 'rbenv rehash'
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+
6
+ private
7
+
8
+ def install_rbenv(shell)
9
+
10
+ shell.with_stat('Installing rbenv') do
11
+ shell.exec "git clone https://github.com/rbenv/rbenv.git #{shell.home_path}/.rbenv"
12
+ shell.exec "git clone https://github.com/rbenv/ruby-build.git #{shell.home_path}/.rbenv/plugins/ruby-build"
13
+
14
+ bashrc = shell.read_file(shell.home_path + '/.bashrc') || ''
15
+ lines = bashrc.split("\n")
16
+ first_line = nil
17
+ lines.each_with_index do |line,index|
18
+ if line.strip[0] != '#'
19
+ first_line = index
20
+ break
21
+ end
22
+ end
23
+ first_line ||= lines.count
24
+ lines.insert first_line, <<-EORC
25
+
26
+ # Initialize rbenv and ruby.
27
+ export PATH="$HOME/.rbenv/bin:$HOME/.rbenv/plugins/ruby-build/bin:$PATH"
28
+ eval "$(rbenv init -)"
29
+
30
+ EORC
31
+
32
+ bashrc = lines.join("\n")
33
+ shell.write_file(shell.home_path + '/.bashrc', bashrc)
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+
6
+ private
7
+
8
+ def install_ruby(shell)
9
+ shell.with_stat("Install Ruby #{@options[:ruby_version]}") do
10
+ result = shell.exec('which rbenv').to_s.strip
11
+ raise 'failed to install rbenv' if result == ''
12
+
13
+ shell.exec "rbenv install -v #{@options[:ruby_version]}"
14
+ shell.exec "rbenv global #{@options[:ruby_version]}"
15
+
16
+ result = shell.exec('which ruby').to_s.partition("\n")[0].strip
17
+ raise 'ruby not where expected' unless result == shell.home_path + '/.rbenv/shims/ruby' || result == '~/.rbenv/shims/ruby'
18
+
19
+ shell.exec "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
20
+ shell.exec 'gem install bundler'
21
+ shell.exec 'rbenv rehash'
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+
6
+ private
7
+
8
+ def restart_nginx(shell)
9
+ shell.with_stat('Restarting nginx') do
10
+ # test the configuration.
11
+ shell.sudo_exec('nginx -t')
12
+
13
+ # stop the service.
14
+ shell.sudo_exec_ignore_code 'systemctl stop nginx.service'
15
+
16
+ # start the service.
17
+ shell.sudo_exec 'systemctl start nginx.service'
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+ private
6
+
7
+ def ssh_copy_id(shell)
8
+ my_id_file = File.expand_path('~/.ssh/rsa_id.pub')
9
+ if File.exist?(my_id_file)
10
+ my_id = File.read(my_id_file)
11
+ shell.with_stat('Enabling public key authentication') do
12
+
13
+ shell.exec 'if [ ! -d ~/.ssh ]; then mkdir ~/.ssh; fi'
14
+ shell.exec 'chmod 700 ~/.ssh'
15
+
16
+ contents = shell.read_file("#{shell.home_path}/.ssh/authorized_keys")
17
+ if contents
18
+ unless contents.split("\n").find{|k| k.to_s.strip == my_id.strip}
19
+ contents += "\n" unless contents[-1] == "\n"
20
+ contents += my_id.strip + "\n"
21
+ shell.write_file("#{shell.home_path}/.ssh/authorized_keys", contents)
22
+ end
23
+ else
24
+ shell.write_file("#{shell.home_path}/.ssh/authorized_keys", my_id + "\n")
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,16 @@
1
+
2
+ module Incline
3
+ class CLI
4
+ class Prepare
5
+
6
+ private
7
+
8
+ def update_system(shell)
9
+ shell.with_stat('Retrieving updates') { shell.apt_get 'update' }
10
+ shell.with_stat('Updating system') { shell.apt_get 'upgrade' }
11
+ shell.with_stat('Updating kernel') { shell.apt_get 'install linux-generic linux-headers-generic linux-image-generic' }
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,270 @@
1
+ require 'securerandom'
2
+ require 'shells'
3
+ require 'io/console'
4
+ require 'ansi/code'
5
+
6
+ require 'incline/cli/prepare/extend_shell'
7
+ require 'incline/cli/prepare/update_system'
8
+ require 'incline/cli/prepare/ssh_copy_id'
9
+ require 'incline/cli/prepare/config_ssh'
10
+ require 'incline/cli/prepare/install_prereqs'
11
+ require 'incline/cli/prepare/install_db'
12
+ require 'incline/cli/prepare/add_deploy_user'
13
+ require 'incline/cli/prepare/install_rbenv'
14
+ require 'incline/cli/prepare/install_ruby'
15
+ require 'incline/cli/prepare/install_rails'
16
+ require 'incline/cli/prepare/install_flytrap'
17
+ require 'incline/cli/prepare/install_passenger'
18
+ require 'incline/cli/prepare/config_passenger'
19
+ require 'incline/cli/prepare/create_nginx_utils'
20
+ require 'incline/cli/prepare/restart_nginx'
21
+
22
+ module Incline
23
+ class CLI
24
+ ##
25
+ # Defines the 'prepare' command for the CLI.
26
+ class Prepare
27
+
28
+ ##
29
+ # Creates a new 'prepare' command for the CLI.
30
+ def initialize(host_name_or_ip, ssh_user, *options)
31
+ @options = {
32
+ port: 22,
33
+ deploy_user: 'deploy',
34
+ deploy_password: SecureRandom.urlsafe_base64(24),
35
+ admin_password: '',
36
+ ruby_version: '2.3.4',
37
+ rails_version: '4.2.9'
38
+ }
39
+ @options[:host] = host_name_or_ip.to_s.strip
40
+
41
+ raise UsageError.new("The 'host_name_or_ip' parameter is required.", 'prepare') if @options[:host] == ''
42
+
43
+ if @options[:host] =~ /\A(?:\[[0-9a-f:]+\]|[a-z0-9]+(?:\.[a-z0-9]+)*):(?:\d+)\z/i
44
+ h,_,p = @options[:host].rpartition(':')
45
+ @options[:host] = h
46
+ @options[:port] = p.to_i
47
+ end
48
+
49
+ @options[:admin_user] = ssh_user.to_s.strip
50
+
51
+ raise UsageError.new("The 'ssh_user' parameter is required.", 'prepare') if @options[:admin_user] == ''
52
+
53
+ while options.any?
54
+ flag = options.delete_at(0)
55
+ case flag
56
+ when '--ssh-password'
57
+ @options[:admin_password] = options.delete_at(0).to_s.strip
58
+ when '--port'
59
+ @options[:port] = options.delete_at(0).to_s.to_i
60
+ when '--deploy-user'
61
+ @options[:deploy_user] = options.delete_at(0).to_s.strip
62
+ raise UsageError.new("The '--deploy-user' parameter requires a valid username.", 'prepare') unless @options[:deploy_user] =~ /\A[a-z][a-z0-9_]*\z/i
63
+ raise UsageError.new("The '--deploy-user' parameter cannot match the 'ssh_user' parameter.", 'prepare') if @options[:deploy_user] == @options[:admin_user]
64
+ when '--ruby-version'
65
+ @options[:ruby_version] = options.delete_at(0).to_s.strip
66
+ raise UsageError.new("The '--ruby-version' parameter must be at least 2.3.", 'prepare') if @options[:ruby_version].to_f < 2.3
67
+ when '--rails-version'
68
+ @options[:rails_version] = options.delete_at(0).to_s.strip
69
+ raise UsageError.new("The '--rails-version' parameter must be at least 4.2.", 'prepare') if @options[:rails_version].to_f < 4.2
70
+
71
+ # These options can be used to customize the self-signed certificate created initially.
72
+ when '--ssl-country'
73
+ @options[:ssl_country] = options.delete_at(0).to_s.strip
74
+ when '--ssl-state', '--ssl-province'
75
+ @options[:ssl_state] = options.delete_at(0).to_s.strip
76
+ when '--ssl-location', '--ssl-city'
77
+ @options[:ssl_location] = options.delete_at(0).to_s.strip
78
+ when '--ssl-org'
79
+ @options[:ssl_org] = options.delete_at(0).to_s.strip
80
+
81
+ else
82
+ raise UsageError.new("The '#{flag}' parameter is not recognized.", 'prepare')
83
+ end
84
+ end
85
+
86
+ @options[:port] = 22 unless (1..65535).include?(@options[:port])
87
+ if @options[:admin_password].to_s.strip == ''
88
+ print 'Please enter the sudo password: '
89
+ @options[:admin_password] = STDIN.noecho(&:gets).to_s.strip
90
+ puts ''
91
+ if @options[:admin_password].to_s.strip == ''
92
+ puts 'WARNING: Sudo password is blank and script may fail because of this.'
93
+ end
94
+ end
95
+
96
+ @options[:ssl_country] = 'US' if @options[:ssl_country].to_s == ''
97
+ @options[:ssl_state] = 'Pennsylvania' if @options[:ssl_state].to_s == ''
98
+ @options[:ssl_location] = 'Pittsburgh' if @options[:ssl_location].to_s == ''
99
+ @options[:ssl_org] = 'WEB' if @options[:ssl_org].to_s == ''
100
+
101
+ end
102
+
103
+ ##
104
+ # Prepares an Ubuntu server with NGINX and Passenger to run Rails applications.
105
+ #
106
+ # Set 'host_name_or_ip' to the DNS name or IP address for the host.
107
+ # Set 'ssh_user' to the user name you want to access the host as.
108
+ # This user must be able to run 'sudo' commands and must not be 'root'.
109
+ #
110
+ # You can provide a port value either appended to the host name or as a
111
+ # separate argument using the '--port' option.
112
+ # e.g. ssh.example.com:22 or --port 22
113
+ #
114
+ # If you are configured with key authentication to the host then you don't
115
+ # need to provide an SSH password to connect. However, this password is
116
+ # also used to run sudo commands. You can specify a password on the command
117
+ # line by using the '--ssh-password' option. If you do not, then the script
118
+ # will prompt you for a "sudo" password to use. You can leave this blank,
119
+ # but the script will warn you that it may lead to failure. Obviously, if
120
+ # you are configured with NOPASSWD in the sudoers file, then you can safely
121
+ # leave the password blank.
122
+ #
123
+ # By default, a deployment user named 'deploy' will be created on the host.
124
+ # If a user by this name already exists, that user will be removed first.
125
+ # This means that any data stored in the user profile will be destroyed by
126
+ # the script prior to creating the new user. You can change the name of the
127
+ # deployment user using the '--deploy-user' option.
128
+ # e.g. --deploy_user bob
129
+ #
130
+ # The script will install 'rbenv' under the deploy user's account and then
131
+ # install Ruby followed by Rails. The default Ruby version installed is
132
+ # 2.3.4 and the Rails version installed is 4.2.9. To change these versions
133
+ # use the '--ruby-version' and '--rails-version' options. The Ruby version
134
+ # must be at least 2.3 and the rails version must be at least 4.2.
135
+ #
136
+ def run
137
+ # reset the host info.
138
+ @host_info = nil
139
+
140
+ admin_shell do |admin|
141
+ # test the connection and sudo capabilities.
142
+ admin.sudo_stat_exec 'Testing connection', 'ls -al /root'
143
+
144
+ # retrieve the host info now that we are connected.
145
+ @host_info = admin.host_info
146
+ raise CliError, "Host OS (#{host_id}) is not supported." unless [ :ubuntu ].include?(host_id)
147
+
148
+ # update the system and configure SSH.
149
+ update_system admin
150
+ ssh_copy_id(admin) unless @options[:admin_password] == ''
151
+ config_ssh admin
152
+ end
153
+ # end the session and reconnect to take advantage of the SSH reset done at the end of config_ssh
154
+ admin_shell do |admin|
155
+ # install ruby prerequisites and mariadb.
156
+ install_prereqs admin
157
+ install_db admin
158
+
159
+ # add the deploy user.
160
+ add_deploy_user admin
161
+
162
+ # log in as deploy user
163
+ deploy_shell do |deploy|
164
+ # enable key auth.
165
+ ssh_copy_id deploy
166
+
167
+ # install rbenv.
168
+ install_rbenv deploy
169
+ end
170
+
171
+ # log out and then back in to load rbenv
172
+ deploy_shell do |deploy|
173
+ # install ruby & rails
174
+ install_ruby deploy
175
+ install_rails deploy
176
+
177
+ # one more fun little addition, we'll add the flytrap app to catch path attacks.
178
+ install_flytrap deploy
179
+ end
180
+ # done with the deploy user, so log out of that session.
181
+
182
+ # install Phusion Passenger to the host and then configure it.
183
+ install_passenger admin
184
+ config_passenger admin
185
+
186
+ # create a few helper utilities to test and reload the configuration.
187
+ create_nginx_utils admin
188
+
189
+ # then restart nginx.
190
+ restart_nginx admin
191
+
192
+ puts 'Testing nginx server...'
193
+ admin.exec_ignore_code 'curl http://localhost/this-is-a-test'
194
+ admin.exec "curl #{flytrap_path}"
195
+ end
196
+
197
+ puts ''
198
+ puts ANSI.ansi(:bold, :white) { 'Host preparation completed.' }
199
+ puts ''
200
+ puts 'Deployment User'
201
+ puts ('-' * 70)
202
+ puts "User: " + ANSI.ansi(:bold) { @options[:deploy_user] }
203
+ puts "Password: " + ANSI.ansi(:bold) { @options[:deploy_password] }
204
+ puts "Home: " + ANSI.ansi(:bold) { @options[:deploy_home] }
205
+ puts ''
206
+ puts 'Server Test Path'
207
+ puts ('-' * 70)
208
+ puts ANSI.ansi(:bold) { flytrap_path }
209
+
210
+ logfile.flush
211
+ logfile.close
212
+ @logfile = nil
213
+
214
+ end
215
+
216
+
217
+ private
218
+
219
+ def host_info
220
+ @host_info ||= {}
221
+ end
222
+
223
+ def host_id
224
+ host_info['ID'] ||= :unknown
225
+ end
226
+
227
+
228
+ def logfile
229
+ @logfile ||=
230
+ begin
231
+ dir = File.expand_path('~/incline-logs')
232
+ Dir.mkdir(dir) unless Dir.exist?(dir)
233
+ File.open(File.expand_path("#{dir}/prepare-#{@options[:host]}.log"), 'wt')
234
+ end
235
+
236
+ end
237
+
238
+ def admin_shell
239
+ Shells::SshSession.new(
240
+ host: @options[:host],
241
+ port: @options[:port],
242
+ user: @options[:admin_user],
243
+ password: @options[:admin_password],
244
+ retrieve_exit_code: true,
245
+ on_non_zero_exit_code: :raise,
246
+ silence_timeout: 5
247
+ ) do |sh|
248
+ extend_shell sh, '# '
249
+ yield sh
250
+ end
251
+ end
252
+
253
+ def deploy_shell
254
+ Shells::SshSession.new(
255
+ host: @options[:host],
256
+ port: @options[:port],
257
+ user: @options[:deploy_user],
258
+ password: @options[:deploy_password],
259
+ retrieve_exit_code: true,
260
+ on_non_zero_exit_code: :raise,
261
+ silence_timeout: 5
262
+ ) do |sh|
263
+ extend_shell sh, '$ '
264
+ yield sh
265
+ end
266
+ end
267
+
268
+ end
269
+ end
270
+ end
@@ -8,38 +8,68 @@ module Incline
8
8
 
9
9
  ##
10
10
  # Creates a new 'usage' command for the CLI.
11
- def initialize
12
-
11
+ def initialize(command = nil)
12
+ @command = command ? command.to_s.downcase.to_sym : nil
13
13
  end
14
14
 
15
15
  ##
16
16
  # Shows usage information for the application.
17
17
  def run
18
-
19
- commands = Incline::CLI::valid_commands.sort{|a,b| a[0] <=> b[0]}
20
-
21
- msg = ANSI.ansi(:bold) { "Usage: #{$PROGRAM_NAME} command <arguments>" }
22
- msg += "\nValid Commands:\n"
23
-
24
- commands.each do |(name,klass,params)|
25
- comment = get_run_comment(klass)
26
- comment = "(No description)" if comment.to_s.strip == ''
27
- comment = ' ' + comment.gsub("\n", "\n ")
28
- msg += " #{name}"
29
- pend = ''
30
- params.each do |t,p|
31
- msg += ' '
32
- if t == :req
33
- msg += p.to_s
34
- elsif t == :opt
35
- msg += '[' + p.to_s
36
- pend += ']'
37
- else
38
- msg += '<...>'
18
+ msg = nil
19
+
20
+ if @command
21
+ command = Incline::CLI::valid_commands.find{|c| c[0] == @command}
22
+ if command
23
+ msg = "Usage: #{$PROGRAM_NAME} #{command[0]}"
24
+ pend = ''
25
+ command[2].each do |t,p|
26
+ msg += ' '
27
+ if t == :req
28
+ msg += p.to_s
29
+ elsif t == :opt
30
+ msg += '[' + p.to_s
31
+ pend += ']'
32
+ else
33
+ msg += '<...>'
34
+ end
35
+ msg += pend
36
+ end
37
+ msg = ANSI.ansi(:bold) { msg }
38
+ msg += "\n"
39
+ comment = get_run_comment(command[1])
40
+ comment = "(No additional information)" if comment.to_s.strip == ''
41
+ comment = ' ' + comment.gsub("\n", "\n ")
42
+ msg += comment + "\n"
43
+ end
44
+ end
45
+
46
+ unless msg
47
+ commands = Incline::CLI::valid_commands.sort{|a,b| a[0] <=> b[0]}
48
+
49
+ msg = ANSI.ansi(:bold) { "Usage: #{$PROGRAM_NAME} command <arguments>" }
50
+ if @command
51
+ msg += "\nInvalid Command: #{@command}\n"
52
+ end
53
+ msg += "\nValid Commands:\n"
54
+ commands.each do |(name,klass,params)|
55
+ msg += " #{name}"
56
+ pend = ''
57
+ params.each do |t,p|
58
+ msg += ' '
59
+ if t == :req
60
+ msg += p.to_s
61
+ elsif t == :opt
62
+ msg += '[' + p.to_s
63
+ pend += ']'
64
+ else
65
+ msg += '<...>'
66
+ end
39
67
  end
68
+ msg += pend + "\n"
40
69
  end
41
- msg += "\n" + comment + "\n"
70
+
42
71
  end
72
+
43
73
 
44
74
  STDOUT.print msg
45
75
  msg