# File lib/phusion_passenger/standalone/start_command.rb, line 42 def self.description return "Start Phusion Passenger Standalone." end
# File lib/phusion_passenger/standalone/start_command.rb, line 46 def initialize(args) super(args) @console_mutex = Mutex.new @termination_pipe = IO.pipe @threads = [] @interruptable_threads = [] @plugin = PhusionPassenger::Plugin.new('standalone/start_command', self, @options) end
# File lib/phusion_passenger/standalone/start_command.rb, line 55 def run parse_my_options sanity_check_options ensure_nginx_installed determine_various_resource_locations require_app_finder @app_finder = AppFinder.new(@args, @options) @apps = @app_finder.scan @plugin.call_hook(:found_apps, @apps) extra_controller_options = {} @plugin.call_hook(:before_creating_nginx_controller, extra_controller_options) create_nginx_controller(extra_controller_options) begin start_nginx show_intro_message daemonize if @options[:daemonize] Thread.abort_on_exception = true @plugin.call_hook(:nginx_started, @nginx) ######################## ######################## watch_log_files_in_background if should_watch_logs? wait_until_nginx_has_exited rescue Interrupt stop_threads stop_nginx exit 2 rescue SignalException => signal stop_threads stop_nginx if signal.message == 'SIGINT' || signal.message == 'SIGTERM' exit 2 else raise end rescue Exception => e stop_threads stop_nginx raise ensure stop_threads end ensure if @temp_dir FileUtils.rm_rf(@temp_dir) rescue nil end @plugin.call_hook(:cleanup) end
# File lib/phusion_passenger/standalone/start_command.rb, line 254 def check_port(address, port) begin socket = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0) sockaddr = Socket.pack_sockaddr_in(port, address) begin socket.connect_nonblock(sockaddr) rescue Errno::ENOENT, Errno::EINPROGRESS, Errno::EAGAIN, Errno::EWOULDBLOCK if select(nil, [socket], nil, 0.1) begin socket.connect_nonblock(sockaddr) rescue Errno::EISCONN end else raise Errno::ECONNREFUSED end end return true rescue Errno::ECONNREFUSED return false ensure socket.close if socket end end
# File lib/phusion_passenger/standalone/start_command.rb, line 278 def check_port_availability if !@options[:socket_file] && check_port(@options[:address], @options[:port]) error "The address #{@options[:address]}:#{@options[:port]} is already " << "in use by another process, perhaps another Phusion Passenger " << "Standalone instance.\n\n" << "If you want to run this Phusion Passenger Standalone instance on " << "another port, use the -p option, like this:\n\n" << " passenger start -p #{@options[:port] + 1}" exit 1 end end
Most platforms don't allow non-root processes to bind to a port lower than 1024. Check whether this is the case for the current platform and if so, tell the user that it must re-run Phusion Passenger Standalone with sudo.
# File lib/phusion_passenger/standalone/start_command.rb, line 233 def check_port_bind_permission_and_display_sudo_suggestion if !@options[:socket_file] && @options[:port] < 1024 && Process.euid != 0 begin TCPServer.new('127.0.0.1', @options[:port]).close rescue Errno::EACCES require 'phusion_passenger/platform_info/ruby' myself = %xwhoami`.strip error "Only the 'root' user can run this program on port #{@options[:port]}. " << "You are currently running as '#{myself}'. Please re-run this program " << "with root privileges with the following command:\n\n" << " #{PlatformInfo.ruby_sudo_command} passenger start #{@original_args.join(' ')} --user=#{myself}\n\n" << "Don't forget the '--user' part! That will make Phusion Passenger Standalone " << "drop root privileges and switch to '#{myself}' after it has obtained " << "port #{@options[:port]}." exit 1 end end end
# File lib/phusion_passenger/standalone/start_command.rb, line 394 def daemonize pid = fork if pid # Parent exit!(0) else # Child trap "HUP", "IGNORE" STDIN.reopen("/dev/null", "r") STDOUT.reopen(@options[:log_file], "a") STDERR.reopen(@options[:log_file], "a") STDOUT.sync = true STDERR.sync = true Process.setsid end end
# File lib/phusion_passenger/standalone/start_command.rb, line 526 def default_group_for(username) user = Etc.getpwnam(username) group = Etc.getgrgid(user.gid) return group.name end
# File lib/phusion_passenger/standalone/start_command.rb, line 349 def ensure_directory_exists(dir) if !File.exist?(dir) require_file_utils FileUtils.mkdir_p(dir) end end
# File lib/phusion_passenger/standalone/start_command.rb, line 329 def ensure_nginx_installed if @options[:nginx_bin] && !File.exist?(@options[:nginx_bin]) error "The given Nginx binary '#{@options[:nginx_bin]}' does not exist." exit 1 end home = Etc.getpwuid.dir @runtime_dir = "#{GLOBAL_STANDALONE_RESOURCE_DIR}/#{runtime_version_string}" if !File.exist?("#{nginx_dir}/sbin/nginx") if Process.euid == 0 install_runtime else @runtime_dir = "#{home}/#{LOCAL_STANDALONE_RESOURCE_DIR}/#{runtime_version_string}" if !File.exist?("#{nginx_dir}/sbin/nginx") install_runtime end end end end
# File lib/phusion_passenger/standalone/start_command.rb, line 308 def install_runtime require 'phusion_passenger/standalone/runtime_installer' installer = RuntimeInstaller.new( :source_root => SOURCE_ROOT, :support_dir => passenger_support_files_dir, :nginx_dir => nginx_dir, :version => @options[:nginx_version], :tarball => @options[:nginx_tarball], :binaries_url_root => @options[:binaries_url_root], :plugin => @plugin) installer.start end
Returns the URL that Nginx will be listening on.
# File lib/phusion_passenger/standalone/start_command.rb, line 295 def listen_url if @options[:socket_file] return @options[:socket_file] else result = "http://#{@options[:address]}" if @options[:port] != 80 result << ":#{@options[:port]}" end result << "/" return result end end
# File lib/phusion_passenger/standalone/start_command.rb, line 325 def nginx_dir return "#{@runtime_dir}/nginx-#{@options[:nginx_version]}" end
Config file template helpers ####
# File lib/phusion_passenger/standalone/start_command.rb, line 513 def nginx_listen_address(options = @options, for_ping_port = false) if options[:socket_file] return "unix:" + File.expand_path(options[:socket_file]) else if for_ping_port port = options[:ping_port] else port = options[:port] end return "#{options[:address]}:#{port}" end end
# File lib/phusion_passenger/standalone/start_command.rb, line 115 def parse_my_options description = "Starts Phusion Passenger Standalone and serve one or more Ruby web applications." parse_options!("start [directory]", description) do |opts| opts.on("-a", "--address HOST", String, wrap_desc("Bind to HOST address (default: #{@options[:address]})")) do |value| @options[:address] = value @options[:tcp_explicitly_given] = true end opts.on("-p", "--port NUMBER", Integer, wrap_desc("Use the given port number (default: #{@options[:port]})")) do |value| @options[:port] = value @options[:tcp_explicitly_given] = true end opts.on("-S", "--socket FILE", String, wrap_desc("Bind to Unix domain socket instead of TCP socket")) do |value| @options[:socket_file] = value end opts.separator "" opts.on("-e", "--environment ENV", String, wrap_desc("Framework environment (default: #{@options[:env]})")) do |value| @options[:env] = value end opts.on("-R", "--rackup FILE", String, wrap_desc("If Rack application detected, run this rackup file")) do |value| ENV["RACKUP_FILE"] = value end opts.on("--max-pool-size NUMBER", Integer, wrap_desc("Maximum number of application processes (default: #{@options[:max_pool_size]})")) do |value| @options[:max_pool_size] = value end opts.on("--min-instances NUMBER", Integer, wrap_desc("Minimum number of processes per application (default: #{@options[:min_instances]})")) do |value| @options[:min_instances] = value end opts.on("--spawn-method NAME", String, wrap_desc("The spawn method to use (default: #{@options[:spawn_method]})")) do |value| @options[:spawn_method] = value end opts.on("--rolling-restarts", wrap_desc("Enable rolling restarts (Enterprise only)")) do @options[:rolling_restarts] = true end opts.on("--resist-deployment-errors", wrap_desc("Enable deployment error resistance (Enterprise only)")) do @options[:resist_deployment_errors] = true end opts.on("--union-station-gateway HOST:PORT", String, wrap_desc("Specify Union Station Gateway host and port")) do |value| host, port = value.split(":", 2) port = port.to_i port = 443 if port == 0 @options[:union_station_gateway_address] = host @options[:union_station_gateway_port] = port.to_i end opts.on("--union-station-key KEY", String, wrap_desc("Specify Union Station key")) do |value| @options[:union_station_key] = value end opts.separator "" opts.on("--ping-port NUMBER", Integer, wrap_desc("Use the given port number for checking whether Nginx is alive (default: same as the normal port)")) do |value| @options[:ping_port] = value end @plugin.call_hook(:parse_options, opts) opts.separator "" opts.on("-d", "--daemonize", wrap_desc("Daemonize into the background")) do @options[:daemonize] = true end opts.on("--user USERNAME", String, wrap_desc("User to run as. Ignored unless running as root.")) do |value| @options[:user] = value end opts.on("--log-file FILENAME", String, wrap_desc("Where to write log messages (default: console, or /dev/null when daemonized)")) do |value| @options[:log_file] = value end opts.on("--pid-file FILENAME", String, wrap_desc("Where to store the PID file")) do |value| @options[:pid_file] = value end opts.on("--nginx-bin FILENAME", String, wrap_desc("Nginx binary to use as core")) do |value| @options[:nginx_bin] = value end opts.on("--nginx-version VERSION", String, wrap_desc("Nginx version to use as core (default: #{@options[:nginx_version]})")) do |value| @options[:nginx_version] = value end opts.on("--nginx-tarball FILENAME", String, wrap_desc("If Nginx needs to be installed, then the given tarball will " + "be used instead of downloading from the Internet")) do |value| @options[:nginx_tarball] = File.expand_path(value) end opts.on("--binaries-url-root URL", String, wrap_desc("If Nginx needs to be installed, then the specified URL will be " + "checked for binaries prior to a local build.")) do |value| @options[:binaries_url_root] = value end end @plugin.call_hook(:done_parsing_options) end
# File lib/phusion_passenger/standalone/start_command.rb, line 321 def passenger_support_files_dir return "#{@runtime_dir}/support" end
# File lib/phusion_passenger/standalone/start_command.rb, line 111 def require_app_finder require 'phusion_passenger/standalone/app_finder' unless defined?(AppFinder) end
# File lib/phusion_passenger/standalone/start_command.rb, line 107 def require_file_utils require 'fileutils' unless defined?(FileUtils) end
# File lib/phusion_passenger/standalone/start_command.rb, line 221 def sanity_check_options if @options[:tcp_explicitly_given] && @options[:socket_file] error "You cannot specify both --address/--port and --socket. Please choose either one." exit 1 end check_port_bind_permission_and_display_sudo_suggestion check_port_availability end
# File lib/phusion_passenger/standalone/start_command.rb, line 290 def should_watch_logs? return !@options[:daemonize] && @options[:log_file] != "/dev/null" end
# File lib/phusion_passenger/standalone/start_command.rb, line 377 def show_intro_message puts "=============== Phusion Passenger Standalone web server started ===============" puts "PID file: #{@options[:pid_file]}" puts "Log file: #{@options[:log_file]}" puts "Environment: #{@options[:env]}" puts "Accessible via: #{listen_url}" puts if @options[:daemonize] puts "Serving in the background as a daemon." else puts "You can stop Phusion Passenger Standalone by pressing Ctrl-C." end puts "===============================================================================" end
# File lib/phusion_passenger/standalone/start_command.rb, line 356 def start_nginx begin @nginx.start rescue DaemonController::AlreadyStarted begin pid = @nginx.pid rescue SystemCallError, IOError pid = nil end if pid error "Phusion Passenger Standalone is already running on PID #{pid}." else error "Phusion Passenger Standalone is already running." end exit 1 rescue DaemonController::StartError => e error "Could not start Passenger Nginx core:\n#{e}" exit 1 end end
# File lib/phusion_passenger/standalone/start_command.rb, line 486 def stop_nginx @console_mutex.synchronize do STDOUT.write("Stopping web server...") STDOUT.flush @nginx.stop STDOUT.puts " done" STDOUT.flush end end
# File lib/phusion_passenger/standalone/start_command.rb, line 496 def stop_threads if !@termination_pipe[1].closed? @termination_pipe[1].write("x") @termination_pipe[1].close end @interruptable_threads.each do |thread| thread.terminate end @interruptable_threads = [] @threads.each do |thread| thread.join end @threads = [] end
Wait until the termination pipe becomes readable (a hint for threads to shut down), or until the timeout has been reached. Returns true if the termination pipe became readable, false if the timeout has been reached.
# File lib/phusion_passenger/standalone/start_command.rb, line 414 def wait_on_termination_pipe(timeout) ios = select([@termination_pipe[0]], nil, nil, timeout) return !ios.nil? end
# File lib/phusion_passenger/standalone/start_command.rb, line 464 def wait_until_nginx_has_exited # Since Nginx is not our child process (it daemonizes or we daemonize) # we cannot use Process.waitpid to wait for it. A busy-sleep-loop with # Process.kill(0, pid) isn't very efficient. Instead we do this: # # Connect to Nginx and wait until Nginx disconnects the socket because of # timeout. Keep doing this until we can no longer connect. while true if @options[:socket_file] socket = UNIXSocket.new(@options[:socket_file]) else socket = TCPSocket.new(@options[:address], nginx_ping_port) end begin socket.read rescue nil ensure socket.close rescue nil end end rescue Errno::ECONNREFUSED, Errno::ECONNRESET end
# File lib/phusion_passenger/standalone/start_command.rb, line 419 def watch_log_file(log_file) if File.exist?(log_file) backward = 0 else # tail bails out if the file doesn't exist, so wait until it exists. while !File.exist?(log_file) sleep 1 end backward = 10 end IO.popen("tail -f -n #{backward} \"#{log_file}\"", "rb") do |f| begin while true begin line = f.readline @console_mutex.synchronize do STDOUT.write(line) STDOUT.flush end rescue EOFError break end end ensure Process.kill('TERM', f.pid) end end end
# File lib/phusion_passenger/standalone/start_command.rb, line 449 def watch_log_files_in_background @apps.each do |app| thread = Thread.new do watch_log_file("#{app[:root]}/log/#{@options[:env]}.log") end @threads << thread @interruptable_threads << thread end thread = Thread.new do watch_log_file(@options[:log_file]) end @threads << thread @interruptable_threads << thread end