#!/usr/bin/env ruby require 'drb' require 'rubygems' require 'cmdparse' require 'yaml' require 'highline' UI = HighLine.new DEFAULT_CONFIG = "conf/scgi.yaml" def safe(error) begin yield rescue Object STDERR.puts("ERROR: #{error}: #$!") exit 1 end end def load_config(file) safe("Could not load config") do return YAML.load_file(file) end end def connect(url) conn = nil safe("Can't connect to #{url}") do conn = DRbObject.new(nil, url) end safe("Failed communicating with #{url}") do yield conn end end def deduce_url(settings) settings[:control_url] || load_config(settings[:config] || DEFAULT_CONFIG)[:control_url] end def defaults(settings) defaults = nil if settings[:merge] UI.say("Merging with previous settings.") defaults = load_config(settings[:config] || DEFAULT_CONFIG) else defaults = { :env => "production", :host => "127.0.0.1", :port => 9999, :logfile => "log/scgi.log", :config => DEFAULT_CONFIG } end settings = defaults.merge(settings) # fix up the stuff that's not quite right yet settings[:control_url] = "druby://127.0.0.1:#{settings[:port]-1000}" settings[:port] = settings[:port].to_i settings[:throttle] = settings[:throttle].to_i if settings[:throttle] settings[:maxconns] = settings[:maxconns].to_i if settings[:maxconns] return settings end def configure(settings) settings = defaults(settings) pass = settings[:password] || UI.ask("What password do you want? ") salting = ('a' .. 'z').to_a + ('A' .. 'Z').to_a + ('0' .. '9').to_a settings[:password] = pass.crypt(salting[rand(salting.length)] + salting[rand(salting.length)]) # great, they are not idiots. Well, they can read --help at least. safe("Could not write config") do open(settings[:config],"w") {|f| f.write(YAML.dump(settings)) } UI.say("Configuration settings written to #{settings[:config]}") end end def password if not $password $password = UI.ask("Password: ") end $password end def start(cmd, config) fork do exec cmd, config end end def status(url) connect(url) do |conn| s = conn.status(password) times = s[:systimes] puts <<-END #{UI.color("Status as of #{s[:time]}:",:green,:bold)} PID: #{s[:pid]}\tStarted: #{s[:started]}\tEnvironment: #{s[:env]} Connected Requests: #{s[:conns]} Conns/Second: #{s[:conns_second] || "Not Set"} Max Simultaneous Conns: #{s[:max_conns]} Shutdown Started: #{s[:shutdown]} Processing Time: #{times.utime} #{times.stime} #{times.cutime} #{times.cstime} Current Settings: #{s[:settings].to_yaml.gsub(/:([a-z])/, ' \1')} END end end def reconfigure(url) connect(url) do |conn| conn.reconfigure(password) end end def stop(url, force) connect(url) do |conn| conn.shutdown(password, force) end end def restart(url, force) connect(url) do |conn| conn.restart(password, force) end end def monitor(url) connect(url) do |conn| while true sleep 3 puts "\e[2J" status(url) end end end def make_command(parent, name, desc, options) cmd = CmdParse::Command.new(name, false ) cmd.short_desc = desc settings = {} cmd.options = CmdParse::OptionParserWrapper.new do |opt| options.each do |short, long, info, symbol| opt.on(short, long, info) {|val| settings[symbol] = val} end end cmd.set_execution_block do |args| yield(settings, args) end parent.add_command(cmd) end cmd = CmdParse::CommandParser.new( true ) cmd.program_name = "scgi_nitro" cmd.program_version = [0, 4, 0] cmd.options = CmdParse::OptionParserWrapper.new do |opt| opt.separator "Global options:" opt.on("--verbose", "Be verbose when outputting info") {|t| $verbose = true } end cmd.add_command( CmdParse::HelpCommand.new ) cmd.add_command( CmdParse::VersionCommand.new ) make_command(cmd, 'config', "Configure the SCGI servers", [['-e','--env STRING','Nitro environment', :env], ['-h','--host STRING', 'IP address to bind as server', :host], ['-p','--port NUMBER', 'Port to bind to (starts at 9999)', :port], ['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url], ['-l','--log-file PATH', 'Use a different log from from log/scgi.log', :logfile], ['-t','--throttle NUMBER', 'Max conn/second to allow.', :conns_second], ['-m','--max-conns NUMBER', 'Max simultaneous connections before the busy message', :maxconns], ['-P','--moron-mode PASSWORD', 'You are an idiot and you want your password on the command line', :password], ['-M','--merge', 'Merge new settings with previous rather than defaults', :merge], ['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args| configure(settings) end make_command(cmd, 'start', "Start the application", [['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url], ['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args| cmd = File.dirname(__FILE__) + "/scgi_service" start(cmd, settings[:config] || DEFAULT_CONFIG) end make_command(cmd, 'reconfig', "Reconfigure the SCGI servers with a new config", [['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url], ['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args| reconfigure(deduce_url(settings)) end make_command(cmd, 'status', "Get status", [['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url], ['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args| status(deduce_url(settings)) end make_command(cmd, 'stop', "Stop the application", [['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url], ['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config], ['-f','--force', 'Forced shutdown rather than graceful (default graceful)', :force]]) do |settings, args| stop(deduce_url(settings), settings[:force] || false) end make_command(cmd, 'restart', "Restart the application", [['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url], ['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config], ['-f','--force', 'Forced shutdown rather than graceful (default graceful)', :force]]) do |settings, args| restart(deduce_url(settings), settings[:force] || false) end make_command(cmd, 'monitor', "Monitor the application", [['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url], ['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args| monitor(deduce_url(settings)) end cmd.parse