class PhusionPassenger::Standalone::StartCommand

Public Class Methods

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

        require 'phusion_passenger/standalone/runtime_locator'
        @runtime_locator = RuntimeLocator.new(@options[:runtime_dir],
                @options[:nginx_version])
        ensure_runtime_installed
        exit if @options[:runtime_check_only]
        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
                if @options[:daemonize]
                        if PlatformInfo.ruby_supports_fork?
                                daemonize
                        else
                                daemonize_without_fork
                        end
                end
                Thread.abort_on_exception = true
                @plugin.call_hook(:nginx_started, @nginx)
                ########################
                ########################
                touch_temp_dir_in_background
                watch_log_files_in_background if should_watch_logs?
                wait_until_nginx_has_exited if should_wait_until_nginx_has_exited?
        rescue Interrupt
                begin_shutdown
                stop_threads
                stop_nginx
                exit 2
        rescue SignalException => signal
                begin_shutdown
                stop_threads
                stop_nginx
                if signal.message == 'SIGINT' || signal.message == 'SIGTERM'
                        exit 2
                else
                        raise
                end
        rescue Exception => e
                begin_shutdown
                stop_threads
                stop_nginx
                raise
        ensure
                begin_shutdown
                begin
                        stop_touching_temp_dir_in_background if should_wait_until_nginx_has_exited?
                        stop_threads
                ensure
                        finalize_shutdown
                end
        end
ensure
        if @temp_dir
                FileUtils.remove_entry_secure(@temp_dir) rescue nil
        end
        @plugin.call_hook(:cleanup)
end

Private Instance Methods

begin_shutdown() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 579
def begin_shutdown
        return if @shutting_down
        @shutting_down = 1
        trap("INT", &method(:signal_during_shutdown))
        trap("TERM", &method(:signal_during_shutdown))
end
check_port(host_name, port) click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 319
def check_port(host_name, port)
        channel = java.nio.channels.SocketChannel.open
        begin
                address = java.net.InetSocketAddress.new(host_name, port)
                channel.configure_blocking(false)
                if channel.connect(address)
                        return true
                end

                deadline = Time.now.to_f + 0.1
                done = false
                while true
                        begin
                                if channel.finish_connect
                                        return true
                                end
                        rescue java.net.ConnectException => e
                                if e.message =~ /Connection refused/i
                                        return false
                                else
                                        throw e
                                end
                        end

                        # Not done connecting and no error.
                        sleep 0.01
                        if Time.now.to_f >= deadline
                                return false
                        end
                end
        ensure
                channel.close
        end
end
check_port_availability() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 385
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 295
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 = %x`whoami`.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 502
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
daemonize_without_fork() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 496
def daemonize_without_fork
        STDERR.puts "Unable to daemonize using the current Ruby interpreter " +
                "(#{PlatformInfo.ruby_command}) because it does not support forking."
        exit 1
end
default_group_for(username) click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 673
def default_group_for(username)
        user = Etc.getpwnam(username)
        group = Etc.getgrgid(user.gid)
        return group.name
end
ensure_runtime_installed() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 440
def ensure_runtime_installed
        if @runtime_locator.everything_installed?
                if !File.exist?(@runtime_locator.find_nginx_binary)
                        error "The Nginx binary '#{@runtime_locator.find_nginx_binary}' does not exist."
                        exit 1
                end
        else
                if !@runtime_locator.find_support_dir && PhusionPassenger.natively_packaged?
                        error "Your Phusion Passenger Standalone installation is broken: the support " +
                                "files could not be found. Please reinstall Phusion Passenger Standalone. " +
                                "If this problem persists, please contact your packager."
                        exit 1
                end
                install_runtime(@runtime_locator) || exit(1)
                @runtime_locator.reload
        end
end
finalize_shutdown() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 586
def finalize_shutdown
        @shutting_down = nil
        trap("INT", "DEFAULT")
        trap("TERM", "DEFAULT")
end
install_runtime(runtime_locator) click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 424
def install_runtime(runtime_locator)
        require 'phusion_passenger/standalone/runtime_installer'
        installer = RuntimeInstaller.new(
                :targets     => runtime_locator.install_targets,
                :support_dir => runtime_locator.support_dir_install_destination,
                :nginx_dir   => runtime_locator.nginx_binary_install_destination,
                :lib_dir     => runtime_locator.find_lib_dir || runtime_locator.support_dir_install_destination,
                :nginx_version     => @options[:nginx_version],
                :nginx_tarball     => @options[:nginx_tarball],
                :binaries_url_root => @options[:binaries_url_root],
                :download_binaries => @options.fetch(:download_binaries, true),
                :dont_compile_runtime => @options[:dont_compile_runtime],
                :plugin      => @plugin)
        return installer.run
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 406
def listen_url
        if @options[:socket_file]
                return @options[:socket_file]
        else
                if @options[:ssl]
                        scheme = "https"
                else
                        scheme = "http"
                end
                result = "#{scheme}://#{@options[:address]}"
                if @options[:port] != 80
                        result << ":#{@options[:port]}"
                end
                result << "/"
                return result
        end
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 660
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 127
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("--concurrency-model NAME", String,
                        wrap_desc("The concurrency model to use, either 'process' or 'thread' (default: #{@options[:concurrency_model]}) (Enterprise only)")) do |value|
                        @options[:concurrency_model] = value
                end
                opts.on("--thread-count NAME", Integer,
                        wrap_desc("The number of threads to use when using the 'thread' concurrency model (default: #{@options[:thread_count]}) (Enterprise only)")) do |value|
                        @options[:thread_count] = 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("--no-friendly-error-pages",
                        wrap_desc("Disable passenger_friendly_error_pages")) do
                        @options[:friendly_error_pages] = false
                end
                opts.on("--ssl",
                        wrap_desc("Enable SSL support")) do
                        @options[:ssl] = true
                end
                opts.on("--ssl-certificate PATH", String,
                        wrap_desc("Specify the SSL certificate path")) do |val|
                        @options[:ssl_certificate] = File.expand_path(val)
                end
                opts.on("--ssl-certificate-key PATH", String,
                        wrap_desc("Specify the SSL key path")) do |val|
                        @options[:ssl_certificate_key] = File.expand_path(val)
                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.separator ""
                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
                opts.on("--no-download-binaries",
                        wrap_desc("Never download binaries")) do
                        @options[:download_binaries] = false
                end
                opts.on("--runtime-dir DIRECTORY", String,
                        wrap_desc("Directory to use for Phusion Passenger Standalone runtime files")) do |value|
                        @options[:runtime_dir] = File.expand_path(value)
                end
                opts.on("--runtime-check-only",
                        wrap_desc("Quit after checking whether the Phusion Passenger Standalone runtime files are installed")) do
                        @options[:runtime_check_only] = true
                end
                opts.on("--no-compile-runtime",
                        wrap_desc("Abort if runtime must be compiled")) do
                        @options[:dont_compile_runtime] = true
                end
        end
        @plugin.call_hook(:done_parsing_options)
end
require_file_utils() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 123
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 275
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
        if @options[:ssl] && !@options[:ssl_certificate]
                error "You specified --ssl. Please specify --ssl-certificate as well."
                exit 1
        end
        if @options[:ssl] && !@options[:ssl_certificate_key]
                error "You specified --ssl. Please specify --ssl-certificate-key as well."
                exit 1
        end
        check_port_bind_permission_and_display_sudo_suggestion
        check_port_availability
end
should_wait_until_nginx_has_exited?() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 401
def should_wait_until_nginx_has_exited?
        return !@options[:daemonize]
end
should_watch_logs?() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 397
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 479
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
signal_during_shutdown(signal) click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 592
def signal_during_shutdown(signal)
        if @shutting_down == 1
                @shutting_down += 1
                puts "Ignoring signal #{signal} during shutdown. Send it again to force exit."
        else
                exit!(1)
        end
end
start_nginx() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 458
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 633
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 643
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
stop_touching_temp_dir_in_background() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 601
def stop_touching_temp_dir_in_background
        if @toucher
                begin
                        Process.kill('TERM', @toucher.pid)
                rescue Errno::ESRCH, Errno::ECHILD
                end
                @toucher.close
        end
end
touch_temp_dir_in_background() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 572
def touch_temp_dir_in_background
        require 'shellwords'
        script = Shellwords.escape("#{PhusionPassenger.helper_scripts_dir}/touch-dir.sh")
        dir    = Shellwords.escape(@temp_dir)
        @toucher = IO.popen("sh #{script} #{dir}", "r")
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 522
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 611
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 527
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) rescue nil
                end
        end
end
watch_log_files_in_background() click to toggle source
# File lib/phusion_passenger/standalone/start_command.rb, line 557
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