2018-09-17 12:41:14 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2011-12-05 18:58:23 -05:00
|
|
|
require 'optparse'
|
2018-08-21 21:10:02 -04:00
|
|
|
require_relative 'state_file'
|
|
|
|
require_relative 'const'
|
|
|
|
require_relative 'detect'
|
|
|
|
require_relative 'configuration'
|
2011-12-05 18:58:23 -05:00
|
|
|
require 'uri'
|
|
|
|
require 'socket'
|
2016-02-21 00:01:33 -05:00
|
|
|
|
2011-12-05 18:58:23 -05:00
|
|
|
module Puma
|
|
|
|
class ControlCLI
|
|
|
|
|
2019-11-11 00:08:41 -05:00
|
|
|
COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats thread-backtraces}
|
|
|
|
PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}
|
2012-09-02 08:56:05 -04:00
|
|
|
|
2012-09-25 12:40:00 -04:00
|
|
|
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
2016-02-06 22:00:29 -05:00
|
|
|
@state = nil
|
|
|
|
@quiet = false
|
|
|
|
@pidfile = nil
|
|
|
|
@pid = nil
|
|
|
|
@control_url = nil
|
|
|
|
@control_auth_token = nil
|
|
|
|
@config_file = nil
|
|
|
|
@command = nil
|
2019-11-11 00:10:02 -05:00
|
|
|
@environment = ENV['RACK_ENV'] || ENV['RAILS_ENV']
|
2016-02-06 22:00:29 -05:00
|
|
|
|
|
|
|
@argv = argv.dup
|
2011-12-07 17:48:41 -05:00
|
|
|
@stdout = stdout
|
2012-09-25 12:40:00 -04:00
|
|
|
@stderr = stderr
|
2015-08-15 13:05:59 -04:00
|
|
|
@cli_options = {}
|
2013-08-01 06:04:44 -04:00
|
|
|
|
2012-10-16 01:24:22 -04:00
|
|
|
opts = OptionParser.new do |o|
|
2013-07-19 02:30:15 -04:00
|
|
|
o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})"
|
2012-10-16 01:24:22 -04:00
|
|
|
|
|
|
|
o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@state = arg
|
2012-09-02 08:56:05 -04:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
|
|
|
o.on "-Q", "--quiet", "Not display messages" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@quiet = true
|
2012-09-02 08:56:05 -04:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
|
|
|
o.on "-P", "--pidfile PATH", "Pid file" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@pidfile = arg
|
2012-09-02 08:56:05 -04:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2012-10-16 01:41:23 -04:00
|
|
|
o.on "-p", "--pid PID", "Pid" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@pid = arg.to_i
|
2012-10-16 01:41:23 -04:00
|
|
|
end
|
|
|
|
|
2012-10-16 01:24:22 -04:00
|
|
|
o.on "-C", "--control-url URL", "The bind url to use for the control server" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@control_url = arg
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
|
|
|
o.on "-T", "--control-token TOKEN", "The token to use as authentication for the control server" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@control_auth_token = arg
|
2012-09-02 08:56:05 -04:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2013-07-19 02:30:15 -04:00
|
|
|
o.on "-F", "--config-file PATH", "Puma config script" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@config_file = arg
|
2013-07-19 02:30:15 -04:00
|
|
|
end
|
|
|
|
|
2019-08-10 14:34:35 -04:00
|
|
|
o.on "-e", "--environment ENVIRONMENT",
|
|
|
|
"The environment to run the Rack app on (default development)" do |arg|
|
|
|
|
@environment = arg
|
|
|
|
end
|
|
|
|
|
2012-10-16 01:24:22 -04:00
|
|
|
o.on_tail("-H", "--help", "Show this message") do
|
2012-10-16 01:37:28 -04:00
|
|
|
@stdout.puts o
|
2012-09-02 08:56:05 -04:00
|
|
|
exit
|
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
|
|
|
o.on_tail("-V", "--version", "Show version") do
|
2012-09-02 08:56:05 -04:00
|
|
|
puts Const::PUMA_VERSION
|
|
|
|
exit
|
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
end
|
|
|
|
|
2012-10-16 01:37:28 -04:00
|
|
|
opts.order!(argv) { |a| opts.terminate a }
|
Parse command-line options for pumactl (#1482)
* Parse command-line options for pumactl
Before, we would remove any non-yielding
args, but missed to actually parse them.
Resulting in issues like, --pidfile or
similar args not taking any effect.
* Remove misleading message of puma starting on pumactl exit
This way, when puma shuts down (running from pumactl), it does not end
with "Puma is started" message. Because, the copy is dependent
on the value of `@command`, the same will always point
to `start`, if pumactl was issued a `start`, hence during
exit, this message appeared as misleading.
2018-03-19 16:51:45 -04:00
|
|
|
opts.parse!
|
2013-08-01 06:04:44 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
@command = argv.shift
|
2015-08-15 13:05:59 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
unless @config_file == '-'
|
2019-10-18 01:49:05 -04:00
|
|
|
environment = @environment || 'development'
|
|
|
|
|
2019-08-10 14:34:35 -04:00
|
|
|
if @config_file.nil?
|
2019-10-18 01:49:05 -04:00
|
|
|
@config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
|
2019-08-10 14:34:35 -04:00
|
|
|
File.exist?(f)
|
|
|
|
end
|
2015-06-11 09:13:33 -04:00
|
|
|
end
|
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
if @config_file
|
2017-03-10 11:30:27 -05:00
|
|
|
config = Puma::Configuration.new({ config_files: [@config_file] }, {})
|
|
|
|
config.load
|
|
|
|
@state ||= config.options[:state]
|
|
|
|
@control_url ||= config.options[:control_url]
|
2016-02-06 22:00:29 -05:00
|
|
|
@control_auth_token ||= config.options[:control_auth_token]
|
2017-03-10 11:30:27 -05:00
|
|
|
@pidfile ||= config.options[:pidfile]
|
2015-06-11 09:13:33 -04:00
|
|
|
end
|
|
|
|
end
|
2013-07-19 02:30:15 -04:00
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
# check present of command
|
2016-02-06 22:00:29 -05:00
|
|
|
unless @command
|
2012-09-02 08:56:05 -04:00
|
|
|
raise "Available commands: #{COMMANDS.join(", ")}"
|
2012-10-16 01:24:22 -04:00
|
|
|
end
|
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
unless COMMANDS.include? @command
|
|
|
|
raise "Invalid command: #{@command}"
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
rescue => e
|
|
|
|
@stdout.puts e.message
|
|
|
|
exit 1
|
|
|
|
end
|
2013-08-01 06:04:44 -04:00
|
|
|
|
2012-10-16 01:24:22 -04:00
|
|
|
def message(msg)
|
2016-02-06 22:00:29 -05:00
|
|
|
@stdout.puts msg unless @quiet
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
def prepare_configuration
|
2016-02-06 22:00:29 -05:00
|
|
|
if @state
|
|
|
|
unless File.exist? @state
|
|
|
|
raise "State file not found: #{@state}"
|
2012-10-16 01:24:22 -04:00
|
|
|
end
|
|
|
|
|
2016-02-26 01:16:07 -05:00
|
|
|
sf = Puma::StateFile.new
|
2016-02-06 22:00:29 -05:00
|
|
|
sf.load @state
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
@control_url = sf.control_url
|
|
|
|
@control_auth_token = sf.control_auth_token
|
|
|
|
@pid = sf.pid
|
|
|
|
elsif @pidfile
|
2012-09-02 08:56:05 -04:00
|
|
|
# get pid from pid_file
|
2019-10-21 08:55:33 -04:00
|
|
|
File.open(@pidfile) { |f| @pid = f.read.to_i }
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
def send_request
|
2016-02-06 22:00:29 -05:00
|
|
|
uri = URI.parse @control_url
|
2013-08-01 06:04:44 -04:00
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
# create server object by scheme
|
2018-08-21 21:10:02 -04:00
|
|
|
server = case uri.scheme
|
2019-11-02 14:51:46 -04:00
|
|
|
when "ssl"
|
|
|
|
require 'openssl'
|
|
|
|
OpenSSL::SSL::SSLSocket.new(
|
|
|
|
TCPSocket.new(uri.host, uri.port),
|
|
|
|
OpenSSL::SSL::SSLContext.new
|
|
|
|
).tap(&:connect)
|
2012-10-16 01:41:23 -04:00
|
|
|
when "tcp"
|
|
|
|
TCPSocket.new uri.host, uri.port
|
|
|
|
when "unix"
|
|
|
|
UNIXSocket.new "#{uri.host}#{uri.path}"
|
|
|
|
else
|
|
|
|
raise "Invalid scheme: #{uri.scheme}"
|
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
if @command == "status"
|
2012-10-16 01:24:22 -04:00
|
|
|
message "Puma is started"
|
|
|
|
else
|
2016-02-06 22:00:29 -05:00
|
|
|
url = "/#{@command}"
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
if @control_auth_token
|
|
|
|
url = url + "?token=#{@control_auth_token}"
|
2012-09-02 08:56:05 -04:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2018-08-21 21:10:02 -04:00
|
|
|
server << "GET #{url} HTTP/1.0\r\n\r\n"
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2018-08-21 21:10:02 -04:00
|
|
|
unless data = server.read
|
2013-06-01 16:55:17 -04:00
|
|
|
raise "Server closed connection before responding"
|
2013-06-01 17:24:33 -04:00
|
|
|
end
|
2013-06-01 16:55:17 -04:00
|
|
|
|
|
|
|
response = data.split("\r\n")
|
|
|
|
|
|
|
|
if response.empty?
|
|
|
|
raise "Server sent empty response"
|
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
(@http,@code,@message) = response.first.split(" ",3)
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
if @code == "403"
|
|
|
|
raise "Unauthorized access to server (wrong auth token)"
|
2013-07-05 19:08:13 -04:00
|
|
|
elsif @code == "404"
|
|
|
|
raise "Command error: #{response.last}"
|
2012-09-02 08:56:05 -04:00
|
|
|
elsif @code != "200"
|
|
|
|
raise "Bad response from server: #{@code}"
|
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
message "Command #{@command} sent success"
|
2019-11-11 00:08:41 -05:00
|
|
|
message response.last if PRINTABLE_COMMANDS.include?(@command)
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
2018-08-21 21:10:02 -04:00
|
|
|
ensure
|
|
|
|
server.close if server && !server.closed?
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
def send_signal
|
2016-02-06 22:00:29 -05:00
|
|
|
unless @pid
|
2012-10-16 01:41:23 -04:00
|
|
|
raise "Neither pid nor control url available"
|
|
|
|
end
|
|
|
|
|
|
|
|
begin
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-12-12 14:56:15 -05:00
|
|
|
case @command
|
|
|
|
when "restart"
|
|
|
|
Process.kill "SIGUSR2", @pid
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-12-12 14:56:15 -05:00
|
|
|
when "halt"
|
|
|
|
Process.kill "QUIT", @pid
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-12-12 14:56:15 -05:00
|
|
|
when "stop"
|
|
|
|
Process.kill "SIGTERM", @pid
|
2012-10-16 01:41:23 -04:00
|
|
|
|
2016-12-12 14:56:15 -05:00
|
|
|
when "stats"
|
|
|
|
puts "Stats not available via pid only"
|
|
|
|
return
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-12-12 14:56:15 -05:00
|
|
|
when "reload-worker-directory"
|
|
|
|
puts "reload-worker-directory not available via pid only"
|
|
|
|
return
|
2014-02-25 08:52:20 -05:00
|
|
|
|
2016-12-12 14:56:15 -05:00
|
|
|
when "phased-restart"
|
|
|
|
Process.kill "SIGUSR1", @pid
|
2013-07-05 19:08:13 -04:00
|
|
|
|
2019-06-23 20:36:06 -04:00
|
|
|
when "status"
|
|
|
|
begin
|
|
|
|
Process.kill 0, @pid
|
|
|
|
puts "Puma is started"
|
|
|
|
rescue Errno::ESRCH
|
|
|
|
raise "Puma is not running"
|
|
|
|
end
|
|
|
|
|
|
|
|
return
|
|
|
|
|
2016-12-12 14:56:15 -05:00
|
|
|
else
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
rescue SystemCallError
|
|
|
|
if @command == "restart"
|
|
|
|
start
|
|
|
|
else
|
|
|
|
raise "No pid '#{@pid}' found"
|
|
|
|
end
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
message "Command #{@command} sent success"
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
def run
|
2018-05-09 14:38:12 -04:00
|
|
|
return start if @command == "start"
|
2012-10-16 01:37:28 -04:00
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
prepare_configuration
|
2013-08-01 06:04:44 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
if Puma.windows?
|
2012-09-02 08:56:05 -04:00
|
|
|
send_request
|
2011-12-05 18:58:23 -05:00
|
|
|
else
|
2016-02-06 22:00:29 -05:00
|
|
|
@control_url ? send_request : send_signal
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
rescue => e
|
|
|
|
message e.message
|
|
|
|
exit 1
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
2013-08-01 06:04:44 -04:00
|
|
|
|
|
|
|
private
|
|
|
|
def start
|
|
|
|
require 'puma/cli'
|
|
|
|
|
2016-03-05 18:57:24 -05:00
|
|
|
run_args = []
|
|
|
|
|
|
|
|
run_args += ["-S", @state] if @state
|
|
|
|
run_args += ["-q"] if @quiet
|
|
|
|
run_args += ["--pidfile", @pidfile] if @pidfile
|
2017-09-22 16:41:34 -04:00
|
|
|
run_args += ["--control-url", @control_url] if @control_url
|
2016-03-05 18:57:24 -05:00
|
|
|
run_args += ["--control-token", @control_auth_token] if @control_auth_token
|
|
|
|
run_args += ["-C", @config_file] if @config_file
|
2019-08-10 14:34:35 -04:00
|
|
|
run_args += ["-e", @environment] if @environment
|
2013-08-08 19:10:26 -04:00
|
|
|
|
2013-08-01 06:04:44 -04:00
|
|
|
events = Puma::Events.new @stdout, @stderr
|
|
|
|
|
2015-10-28 08:18:28 -04:00
|
|
|
# replace $0 because puma use it to generate restart command
|
2015-11-06 14:00:49 -05:00
|
|
|
puma_cmd = $0.gsub(/pumactl$/, 'puma')
|
2015-10-28 08:18:28 -04:00
|
|
|
$0 = puma_cmd if File.exist?(puma_cmd)
|
|
|
|
|
2013-08-01 06:04:44 -04:00
|
|
|
cli = Puma::CLI.new run_args, events
|
|
|
|
cli.run
|
|
|
|
end
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
2013-08-01 06:04:44 -04:00
|
|
|
end
|