# frozen_string_literal: truerequire'optparse'require_relative'state_file'require_relative'const'require_relative'detect'require_relative'configuration'require'uri'require'socket'modulePumaclassControlCLICOMMANDS=%w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats}definitialize(argv,stdout=STDOUT,stderr=STDERR)@state=nil@quiet=false@pidfile=nil@pid=nil@control_url=nil@control_auth_token=nil@config_file=nil@command=nil@environment=ENV['RACK_ENV']@argv=argv.dup@stdout=stdout@stderr=stderr@cli_options={}opts=OptionParser.newdo|o|o.banner="Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})"o.on"-S","--state PATH","Where the state file to use is"do|arg|@state=argendo.on"-Q","--quiet","Not display messages"do|arg|@quiet=trueendo.on"-P","--pidfile PATH","Pid file"do|arg|@pidfile=argendo.on"-p","--pid PID","Pid"do|arg|@pid=arg.to_iendo.on"-C","--control-url URL","The bind url to use for the control server"do|arg|@control_url=argendo.on"-T","--control-token TOKEN","The token to use as authentication for the control server"do|arg|@control_auth_token=argendo.on"-F","--config-file PATH","Puma config script"do|arg|@config_file=argendo.on"-e","--environment ENVIRONMENT","The environment to run the Rack app on (default development)"do|arg|@environment=argendo.on_tail("-H","--help","Show this message")do@stdout.putsoexitendo.on_tail("-V","--version","Show version")doputsConst::PUMA_VERSIONexitendendopts.order!(argv){|a|opts.terminatea}opts.parse!@command=argv.shiftunless@config_file=='-'environment=@environment||'development'if@config_file.nil?@config_file=%W(config/puma/#{environment}.rb config/puma.rb).finddo|f|File.exist?(f)endendif@config_fileconfig=Puma::Configuration.new({config_files: [@config_file]},{})config.load@state||=config.options[:state]@control_url||=config.options[:control_url]@control_auth_token||=config.options[:control_auth_token]@pidfile||=config.options[:pidfile]endend# check present of commandunless@commandraise"Available commands: #{COMMANDS.join(", ")}"endunlessCOMMANDS.include?@commandraise"Invalid command: #{@command}"endrescue=>e@stdout.putse.messageexit1enddefmessage(msg)@stdout.putsmsgunless@quietenddefprepare_configurationif@stateunlessFile.exist?@stateraise"State file not found: #{@state}"endsf=Puma::StateFile.newsf.load@state@control_url=sf.control_url@control_auth_token=sf.control_auth_token@pid=sf.pidelsif@pidfile# get pid from pid_fileFile.open(@pidfile){|f|@pid=f.read.to_i}endenddefsend_requesturi=URI.parse@control_url# create server object by schemeserver=caseuri.schemewhen"ssl"require'openssl'OpenSSL::SSL::SSLSocket.new(TCPSocket.new(uri.host,uri.port),OpenSSL::SSL::SSLContext.new).tap(&:connect)when"tcp"TCPSocket.newuri.host,uri.portwhen"unix"UNIXSocket.new"#{uri.host}#{uri.path}"elseraise"Invalid scheme: #{uri.scheme}"endif@command=="status"message"Puma is started"elseurl="/#{@command}"if@control_auth_tokenurl=url+"?token=#{@control_auth_token}"endserver<<"GET #{url} HTTP/1.0\r\n\r\n"unlessdata=server.readraise"Server closed connection before responding"endresponse=data.split("\r\n")ifresponse.empty?raise"Server sent empty response"end(@http,@code,@message)=response.first.split(" ",3)if@code=="403"raise"Unauthorized access to server (wrong auth token)"elsif@code=="404"raise"Command error: #{response.last}"elsif@code!="200"raise"Bad response from server: #{@code}"endmessage"Command #{@command} sent success"messageresponse.lastif@command=="stats"||@command=="gc-stats"endensureserver.closeifserver&&!server.closed?enddefsend_signalunless@pidraise"Neither pid nor control url available"endbegincase@commandwhen"restart"Process.kill"SIGUSR2",@pidwhen"halt"Process.kill"QUIT",@pidwhen"stop"Process.kill"SIGTERM",@pidwhen"stats"puts"Stats not available via pid only"returnwhen"reload-worker-directory"puts"reload-worker-directory not available via pid only"returnwhen"phased-restart"Process.kill"SIGUSR1",@pidwhen"status"beginProcess.kill0,@pidputs"Puma is started"rescueErrno::ESRCHraise"Puma is not running"endreturnelsereturnendrescueSystemCallErrorif@command=="restart"startelseraise"No pid '#{@pid}' found"endendmessage"Command #{@command} sent success"enddefrunreturnstartif@command=="start"prepare_configurationifPuma.windows?send_requestelse@control_url?send_request:send_signalendrescue=>emessagee.messageexit1endprivatedefstartrequire'puma/cli'run_args=[]run_args+=["-S",@state]if@staterun_args+=["-q"]if@quietrun_args+=["--pidfile",@pidfile]if@pidfilerun_args+=["--control-url",@control_url]if@control_urlrun_args+=["--control-token",@control_auth_token]if@control_auth_tokenrun_args+=["-C",@config_file]if@config_filerun_args+=["-e",@environment]if@environmentevents=Puma::Events.new@stdout,@stderr# replace $0 because puma use it to generate restart commandpuma_cmd=$0.gsub(/pumactl$/,'puma')$0=puma_cmdifFile.exist?(puma_cmd)cli=Puma::CLI.newrun_args,eventscli.runendendend