class PhusionPassenger::Standalone::StartCommand

Public Class Methods

description() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 42
def self.description
        return "Start Phusion Passenger Standalone."
end
new(args) click to toggle source
# 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

Public Instance Methods

run() click to toggle source
# 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

Private Instance Methods

check_port(address, port) click to toggle source
# 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
check_port_availability() click to toggle source
# 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
check_port_bind_permission_and_display_sudo_suggestion() click to toggle source

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
daemonize() click to toggle source
# 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
default_group_for(username) click to toggle source
# 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
ensure_directory_exists(dir) click to toggle source
# 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
ensure_nginx_installed() click to toggle source
# 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
install_runtime() click to toggle source
# 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
listen_url() click to toggle source

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
nginx_dir() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 325
def nginx_dir
        return "#{@runtime_dir}/nginx-#{@options[:nginx_version]}"
end
nginx_listen_address(options = @options, for_ping_port = false) click to toggle source

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
parse_my_options() click to toggle source
# 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
passenger_support_files_dir() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 321
def passenger_support_files_dir
        return "#{@runtime_dir}/support"
end
require_app_finder() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 111
def require_app_finder
        require 'phusion_passenger/standalone/app_finder' unless defined?(AppFinder)
end
require_file_utils() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 107
def require_file_utils
        require 'fileutils' unless defined?(FileUtils)
end
sanity_check_options() click to toggle source
# 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
should_watch_logs?() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 290
def should_watch_logs?
        return !@options[:daemonize] && @options[:log_file] != "/dev/null"
end
show_intro_message() click to toggle source
# 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
start_nginx() click to toggle source
# 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
stop_nginx() click to toggle source
# 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
stop_threads() click to toggle source
# 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_on_termination_pipe(timeout) click to toggle source

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
wait_until_nginx_has_exited() click to toggle source
# 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
watch_log_file(log_file) click to toggle source
# 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
watch_log_files_in_background() click to toggle source
# 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