class PhusionPassenger::Standalone::StartCommand

def self.description

def self.description
	return "Start Phusion Passenger Standalone."
end

def check_port(address, port)

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

def check_port_availability

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

def check_port_bind_permission_and_display_sudo_suggestion

that it must re-run Phusion Passenger Standalone with sudo.
Check whether this is the case for the current platform and if so, tell the user
Most platforms don't allow non-root processes to bind to a port lower than 1024.
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 = `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

def daemonize

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

def default_group_for(username)

def default_group_for(username)
	user = Etc.getpwnam(username)
	group = Etc.getgrgid(user.gid)
	return group.name
end

def ensure_directory_exists(dir)

def ensure_directory_exists(dir)
	if !File.exist?(dir)
		require_file_utils
		FileUtils.mkdir_p(dir)
	end
end

def ensure_nginx_installed

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

def initialize(args)

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

def install_runtime

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

def listen_url

Returns the URL that Nginx will be listening on.
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

def nginx_dir

def nginx_dir
	return "#{@runtime_dir}/nginx-#{@options[:nginx_version]}"
end

def nginx_listen_address(options = @options, for_ping_port = false)

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

def parse_my_options

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("--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

def passenger_support_files_dir

def passenger_support_files_dir
	return "#{@runtime_dir}/support"
end

def require_app_finder

def require_app_finder
	require 'phusion_passenger/standalone/app_finder' unless defined?(AppFinder)
end

def require_file_utils

def require_file_utils
	require 'fileutils' unless defined?(FileUtils)
end

def run

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

def sanity_check_options

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

def should_watch_logs?

def should_watch_logs?
	return !@options[:daemonize] && @options[:log_file] != "/dev/null"
end

def show_intro_message

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

def start_nginx

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

def stop_nginx

def stop_nginx
	@console_mutex.synchronize do
		STDOUT.write("Stopping web server...")
		STDOUT.flush
		@nginx.stop
		STDOUT.puts " done"
		STDOUT.flush
	end
end

def stop_threads

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

def wait_on_termination_pipe(timeout)

the termination pipe became readable, false if the timeout has been reached.
to shut down), or until the timeout has been reached. Returns true if
Wait until the termination pipe becomes readable (a hint for threads
def wait_on_termination_pipe(timeout)
	ios = select([@termination_pipe[0]], nil, nil, timeout)
	return !ios.nil?
end

def wait_until_nginx_has_exited

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

def watch_log_file(log_file)

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

def watch_log_files_in_background

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