<commit_before>#  encoding: utf-8
#
#  Phusion Passenger - https://www.phusionpassenger.com/
#  Copyright (c) 2010-2013 Phusion
#
#  "Phusion Passenger" is a trademark of <NAME> & <NAME>.
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to deal
#  in the Software without restriction, including without limitation the rights
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#  copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in
#  all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#  THE SOFTWARE.
require 'fileutils'
require 'phusion_passenger'
require 'phusion_passenger/abstract_installer'
require 'phusion_passenger/packaging'
require 'phusion_passenger/common_library'
require 'phusion_passenger/platform_info'
require 'phusion_passenger/platform_info/ruby'
require 'phusion_passenger/platform_info/binary_compatibility'
require 'phusion_passenger/standalone/utils'
require 'phusion_passenger/utils/tmpio'

module PhusionPassenger
module Standalone

# Installs the Phusion Passenger Standalone runtime by downloading or compiling
# the Phusion Passenger support binaries and Nginx, and then storing them
# in the designated directories. This installer is entirely non-interactive.
#
# The following option must be given:
# - targets: An array containing at least one of:
#   * :support_binaries - to indicate that you want to install the
#                         Phusion Passenger support binary files.
#   * :nginx - to indicate that you want to install Nginx.
#
# If `targets` contains `:support_binaries`, then you must also specify this
# options:
# - support_dir: The support binary files will be installed here.
#
# If `targets` contains `:nginx`, then you must also specify these options:
# - nginx_dir: Nginx will be installed into this directory.
# - lib_dir: Path to the Phusion Passenger libraries, which Nginx will link to.
#            This may be the same path as `support_dir`; Nginx will be compiled
#            after the support binary files are installed.
# - nginx_version (optional): The Nginx version to download. If not given then a
#   hardcoded version number will be used.
# - nginx_tarball (optional): The location to the Nginx tarball. This tarball *must*
#   contain the Nginx version as specified by +version+. If +tarball+ is given
#   then Nginx will not be downloaded; it will be extracted from this tarball
#   instead.
#
# Other optional options:
# - download_binaries: If true then RuntimeInstaller will attempt to download
#   a precompiled Nginx binary and precompiled Phusion Passenger support binaries
#   from the network, if they exist for the current platform. The default is
#   true. Note that binary downloading only happens when Phusion Passenger is
#   installed from an official release package.
# - binaries_url_root: The URL on which to look for the aforementioned binaries.
#   The default points to the Phusion website.
class RuntimeInstaller < AbstractInstaller
	include Utils

	def initialize(*args)
		super(*args)
		raise ArgumentError, "At least one target must be given" if @targets.nil? || @targets.empty?
		if @targets.include?(:support_binaries)
			if PhusionPassenger.natively_packaged?
				raise ArgumentError, "You cannot specify :support_binaries as a " +
					"target when natively packaged"
			end
			raise ArgumentError, ":support_dir must be given" if !@support_dir
		end
		if @targets.include?(:nginx)
			raise ArgumentError, ":nginx_dir must be given" if !@nginx_dir
			raise ArgumentError, ":lib_dir must be given" if !@lib_dir
		end
	end
	
protected
	def dependencies
		specs = [
			'depcheck_specs/compiler_toolchain',
			'depcheck_specs/ruby',
			'depcheck_specs/gems',
			'depcheck_specs/libs',
			'depcheck_specs/utilities'
		]
		ids = [
			'gcc',
			'g++',
			'gmake',
			'ruby-openssl',
			'rubygems',
			'rake',
			'rack',
			'libcurl-dev',
			'openssl-dev',
			'zlib-dev',
			'pcre-dev',
			'daemon_controller >= 1.1.0'
		].compact
		return [specs, ids]
	end
	
	def users_guide
		return "#{PhusionPassenger.doc_dir}/Users guide Standalone.html"
	end

	def run_steps
		show_welcome_screen if @nginx_dir
		check_whether_os_is_broken
		check_for_download_tool
		download_or_compile_binaries
		puts
		puts "<green><b>All done!</b></green>"
		puts
	end

	def before_install
		super
		@plugin.call_hook(:runtime_installer_start, self) if @plugin
		@working_dir = PhusionPassenger::Utils.mktmpdir("passenger.", PlatformInfo.tmpexedir)
		@nginx_version ||= PREFERRED_NGINX_VERSION
		@download_binaries = true if !defined?(@download_binaries)
		@binaries_url_root ||= BINARIES_URL_ROOT
	end

	def after_install
		super
		FileUtils.remove_entry_secure(@working_dir) if @working_dir
		@plugin.call_hook(:runtime_installer_cleanup) if @plugin
	end

private
	def show_welcome_screen
		render_template 'standalone/welcome',
			:version => @nginx_version,
			:dir => @nginx_dir
		puts
	end

	def check_for_download_tool
		puts "<banner>Checking for basic prerequities...</banner>"
		puts

		require 'phusion_passenger/platform_info/depcheck'
		PlatformInfo::Depcheck.load('depcheck_specs/utilities')
		runner = PlatformInfo::Depcheck::ConsoleRunner.new
		runner.add('download-tool')

		if !runner.check_all
			@download_binaries = false
			puts
			line
			puts
			render_template 'standalone/download_tool_missing',
				:runner => runner
			wait
		end
	end

	def download_or_compile_binaries
		if should_install_support_binaries?
			support_binaries_path = download_support_binaries
		end
		if should_install_nginx?
			nginx_binary_path = download_nginx_binary
		end
		
		should_compile_support_binaries = should_install_support_binaries? &&
			!support_binaries_path
		should_compile_nginx = should_install_nginx? && !nginx_binary_path

		if should_compile_support_binaries || should_compile_nginx
			if @dont_compile_runtime
				@stderr.puts "*** ERROR: Refusing to compile the Phusion Passenger Standalone runtime " +
					"because --no-compile-runtime is given."
				exit(1)
			end
			check_dependencies(false) || exit(1)
			puts
			if should_compile_support_binaries
				check_whether_we_can_write_to(@support_dir) || exit(1)
			end
			if should_compile_nginx
				check_whether_we_can_write_to(@nginx_dir) || exit(1)
			end
		end

		if should_compile_nginx
			nginx_source_dir = download_and_extract_nginx_sources
		end
		if should_compile_support_binaries
			compile_support_binaries
		end
		if should_compile_nginx
			compile_nginx(nginx_source_dir)
		end
	end

	# If this method returns true, then PhusionPassenger.originally_packaged? is also true.
	def should_install_support_binaries?
		return @targets.include?(:support_binaries)
	end

	def should_install_nginx?
		return @targets.include?(:nginx)
	end

	def should_download_binaries?
		return PhusionPassenger.installed_from_release_package? &&
			@download_binaries &&
			@binaries_url_root
	end

	def download_support_binaries
		return nil if !should_download_binaries?

		puts "<banner>Downloading Passenger support binaries for your platform, if available...</banner>"
		basename = "support-#{PlatformInfo.cxx_binary_compatibility_id}.tar.gz"
		url      = "#{@binaries_url_root}/#{PhusionPassenger::VERSION_STRING}/#{basename}"
		tarball  = "#{@working_dir}/#{basename}"
		if !download(url, tarball, :cacert => PhusionPassenger.binaries_ca_cert_path, :use_cache => true)
			puts "<b>No binaries are available for your platform. But don't worry, the " +
				"necessary binaries will be compiled from source instead.</b>"
			puts
			return nil
		end
		
		FileUtils.mkdir_p(@support_dir)
		Dir.chdir(@support_dir) do
			puts "Extracting tarball..."
			result = extract_tarball(tarball)
			return nil if !result

			["PassengerWatchdog", "PassengerHelperAgent", "PassengerLoggingAgent"].each do |exe|
				puts "Checking whether the downloaded #{exe} binary is usable..."
				output = `env LD_BIND_NOW=1 DYLD_BIND_AT_LAUNCH=1 ./agents/#{exe} --test-binary 1`
				if !$? || $?.exitstatus != 0 || output != "PASS\n"
					puts "Binary #{exe} is not usable."
					return nil
				end
			end
			puts "Binary is usable."
			return result
		end
	rescue Interrupt
		exit 2
	end

	def download_nginx_binary
		return false if !should_download_binaries?

		puts "<banner>Downloading Nginx binary for your platform, if available...</banner>"
		basename = "nginx-#{@nginx_version}-#{PlatformInfo.cxx_binary_compatibility_id}.tar.gz"
		url      = "#{@binaries_url_root}/#{PhusionPassenger::VERSION_STRING}/#{basename}"
		tarball  = "#{@working_dir}/#{basename}"
		if !download(url, tarball, :cacert => PhusionPassenger.binaries_ca_cert_path, :use_cache => true)
			puts "<b>No binary available for your platform. But don't worry, the " +<commit_msg>Slightly change Passenger Standalone binary checking message.<commit_after>#  encoding: utf-8
#
#  Phusion Passenger - https://www.phusionpassenger.com/
#  Copyright (c) 2010-2013 Phusion
#
#  "Phusion Passenger" is a trademark of <NAME> & <NAME>.
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to deal
#  in the Software without restriction, including without limitation the rights
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#  copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in
#  all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#  THE SOFTWARE.
require 'fileutils'
require 'phusion_passenger'
require 'phusion_passenger/abstract_installer'
require 'phusion_passenger/packaging'
require 'phusion_passenger/common_library'
require 'phusion_passenger/platform_info'
require 'phusion_passenger/platform_info/ruby'
require 'phusion_passenger/platform_info/binary_compatibility'
require 'phusion_passenger/standalone/utils'
require 'phusion_passenger/utils/tmpio'

module PhusionPassenger
module Standalone

# Installs the Phusion Passenger Standalone runtime by downloading or compiling
# the Phusion Passenger support binaries and Nginx, and then storing them
# in the designated directories. This installer is entirely non-interactive.
#
# The following option must be given:
# - targets: An array containing at least one of:
#   * :support_binaries - to indicate that you want to install the
#                         Phusion Passenger support binary files.
#   * :nginx - to indicate that you want to install Nginx.
#
# If `targets` contains `:support_binaries`, then you must also specify this
# options:
# - support_dir: The support binary files will be installed here.
#
# If `targets` contains `:nginx`, then you must also specify these options:
# - nginx_dir: Nginx will be installed into this directory.
# - lib_dir: Path to the Phusion Passenger libraries, which Nginx will link to.
#            This may be the same path as `support_dir`; Nginx will be compiled
#            after the support binary files are installed.
# - nginx_version (optional): The Nginx version to download. If not given then a
#   hardcoded version number will be used.
# - nginx_tarball (optional): The location to the Nginx tarball. This tarball *must*
#   contain the Nginx version as specified by +version+. If +tarball+ is given
#   then Nginx will not be downloaded; it will be extracted from this tarball
#   instead.
#
# Other optional options:
# - download_binaries: If true then RuntimeInstaller will attempt to download
#   a precompiled Nginx binary and precompiled Phusion Passenger support binaries
#   from the network, if they exist for the current platform. The default is
#   true. Note that binary downloading only happens when Phusion Passenger is
#   installed from an official release package.
# - binaries_url_root: The URL on which to look for the aforementioned binaries.
#   The default points to the Phusion website.
class RuntimeInstaller < AbstractInstaller
	include Utils

	def initialize(*args)
		super(*args)
		raise ArgumentError, "At least one target must be given" if @targets.nil? || @targets.empty?
		if @targets.include?(:support_binaries)
			if PhusionPassenger.natively_packaged?
				raise ArgumentError, "You cannot specify :support_binaries as a " +
					"target when natively packaged"
			end
			raise ArgumentError, ":support_dir must be given" if !@support_dir
		end
		if @targets.include?(:nginx)
			raise ArgumentError, ":nginx_dir must be given" if !@nginx_dir
			raise ArgumentError, ":lib_dir must be given" if !@lib_dir
		end
	end
	
protected
	def dependencies
		specs = [
			'depcheck_specs/compiler_toolchain',
			'depcheck_specs/ruby',
			'depcheck_specs/gems',
			'depcheck_specs/libs',
			'depcheck_specs/utilities'
		]
		ids = [
			'gcc',
			'g++',
			'gmake',
			'ruby-openssl',
			'rubygems',
			'rake',
			'rack',
			'libcurl-dev',
			'openssl-dev',
			'zlib-dev',
			'pcre-dev',
			'daemon_controller >= 1.1.0'
		].compact
		return [specs, ids]
	end
	
	def users_guide
		return "#{PhusionPassenger.doc_dir}/Users guide Standalone.html"
	end

	def run_steps
		show_welcome_screen if @nginx_dir
		check_whether_os_is_broken
		check_for_download_tool
		download_or_compile_binaries
		puts
		puts "<green><b>All done!</b></green>"
		puts
	end

	def before_install
		super
		@plugin.call_hook(:runtime_installer_start, self) if @plugin
		@working_dir = PhusionPassenger::Utils.mktmpdir("passenger.", PlatformInfo.tmpexedir)
		@nginx_version ||= PREFERRED_NGINX_VERSION
		@download_binaries = true if !defined?(@download_binaries)
		@binaries_url_root ||= BINARIES_URL_ROOT
	end

	def after_install
		super
		FileUtils.remove_entry_secure(@working_dir) if @working_dir
		@plugin.call_hook(:runtime_installer_cleanup) if @plugin
	end

private
	def show_welcome_screen
		render_template 'standalone/welcome',
			:version => @nginx_version,
			:dir => @nginx_dir
		puts
	end

	def check_for_download_tool
		puts "<banner>Checking for basic prerequities...</banner>"
		puts

		require 'phusion_passenger/platform_info/depcheck'
		PlatformInfo::Depcheck.load('depcheck_specs/utilities')
		runner = PlatformInfo::Depcheck::ConsoleRunner.new
		runner.add('download-tool')

		if !runner.check_all
			@download_binaries = false
			puts
			line
			puts
			render_template 'standalone/download_tool_missing',
				:runner => runner
			wait
		end
	end

	def download_or_compile_binaries
		if should_install_support_binaries?
			support_binaries_path = download_support_binaries
		end
		if should_install_nginx?
			nginx_binary_path = download_nginx_binary
		end
		
		should_compile_support_binaries = should_install_support_binaries? &&
			!support_binaries_path
		should_compile_nginx = should_install_nginx? && !nginx_binary_path

		if should_compile_support_binaries || should_compile_nginx
			if @dont_compile_runtime
				@stderr.puts "*** ERROR: Refusing to compile the Phusion Passenger Standalone runtime " +
					"because --no-compile-runtime is given."
				exit(1)
			end
			check_dependencies(false) || exit(1)
			puts
			if should_compile_support_binaries
				check_whether_we_can_write_to(@support_dir) || exit(1)
			end
			if should_compile_nginx
				check_whether_we_can_write_to(@nginx_dir) || exit(1)
			end
		end

		if should_compile_nginx
			nginx_source_dir = download_and_extract_nginx_sources
		end
		if should_compile_support_binaries
			compile_support_binaries
		end
		if should_compile_nginx
			compile_nginx(nginx_source_dir)
		end
	end

	# If this method returns true, then PhusionPassenger.originally_packaged? is also true.
	def should_install_support_binaries?
		return @targets.include?(:support_binaries)
	end

	def should_install_nginx?
		return @targets.include?(:nginx)
	end

	def should_download_binaries?
		return PhusionPassenger.installed_from_release_package? &&
			@download_binaries &&
			@binaries_url_root
	end

	def download_support_binaries
		return nil if !should_download_binaries?

		puts "<banner>Downloading Passenger support binaries for your platform, if available...</banner>"
		basename = "support-#{PlatformInfo.cxx_binary_compatibility_id}.tar.gz"
		url      = "#{@binaries_url_root}/#{PhusionPassenger::VERSION_STRING}/#{basename}"
		tarball  = "#{@working_dir}/#{basename}"
		if !download(url, tarball, :cacert => PhusionPassenger.binaries_ca_cert_path, :use_cache => true)
			puts "<b>No binaries are available for your platform. But don't worry, the " +
				"necessary binaries will be compiled from source instead.</b>"
			puts
			return nil
		end
		
		FileUtils.mkdir_p(@support_dir)
		Dir.chdir(@support_dir) do
			puts "Extracting tarball..."
			result = extract_tarball(tarball)
			return nil if !result

			["PassengerWatchdog", "PassengerHelperAgent", "PassengerLoggingAgent"].each do |exe|
				puts "Checking whether the downloaded #{exe} binary is usable..."
				output = `env LD_BIND_NOW=1 DYLD_BIND_AT_LAUNCH=1 ./agents/#{exe} --test-binary 1`
				if !$? || $?.exitstatus != 0 || output != "PASS\n"
					puts "Binary #{exe} is not usable."
					return nil
				end
			end
			puts "Binaries are usable."
			return result
		end
	rescue Interrupt
		exit 2
	end

	def download_nginx_binary
		return false if !should_download_binaries?

		puts "<banner>Downloading Nginx binary for your platform, if available...</banner>"
		basename = "nginx-#{@nginx_version}-#{PlatformInfo.cxx_binary_compatibility_id}.tar.gz"
		url      = "#{@binaries_url_root}/#{PhusionPassenger::VERSION_STRING}/#{basename}"
		tarball  = "#{@working_dir}/#{basename}"
		if !download(url, tarball, :cacert => PhusionPassenger.binaries_ca_cert_path, :use_cache => true)
			puts "<b>No binary available for your platform. But don't worry, the " +
{'fraction_non_alphanumeric': 0.08434244619533605, 'fraction_numerical': 0.00222092961768283, 'mean_word_length': 4.494479953515398, 'pattern_counts': {'":': 6, '<': 35, '<?xml version=': 0, '>': 49, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 58, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4436598', 'n_tokens_mistral': 6550, 'n_tokens_neox': 5846, 'n_words': 1798}