#!/usr/bin/env ruby
# Phusion Passenger - https://www.phusionpassenger.com/
# Copyright (c) 2010-2017 Phusion Holding B.V.
#
# "Passenger", "Phusion Passenger" and "Union Station" are registered
# trademarks of Phusion Holding B.V.
#
# 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.
## Magic comment: begin bootstrap ##
source_root = File.expand_path("..", File.dirname(__FILE__))
$LOAD_PATH.unshift("#{source_root}/src/ruby_supportlib")
begin
require 'rubygems'
rescue LoadError
end
require 'phusion_passenger'
## Magic comment: end bootstrap ##
PhusionPassenger.locate_directories
require 'digest/sha2'
require 'optparse'
require 'fileutils'
require 'tmpdir'
PhusionPassenger.require_passenger_lib 'platform_info/ruby'
PhusionPassenger.require_passenger_lib 'platform_info/openssl'
PhusionPassenger.require_passenger_lib 'abstract_installer'
PhusionPassenger.require_passenger_lib 'utils/terminal_choice_menu'
PhusionPassenger.require_passenger_lib 'utils/shellwords'
DOWNLOAD_OPTION = {
:connect_timeout => 30,
:idle_timeout => 30
}
class Installer < PhusionPassenger::AbstractInstaller
include PhusionPassenger
TerminalChoiceMenu = PhusionPassenger::Utils::TerminalChoiceMenu
def dependencies
specs = [
'depcheck_specs/compiler_toolchain',
'depcheck_specs/ruby',
'depcheck_specs/gems',
'depcheck_specs/libs',
'depcheck_specs/utilities'
]
ids = [
'cc',
'c++',
'download-tool',
'libcurl-dev',
'openssl-dev',
'zlib-dev',
'rake',
'ruby-openssl',
'rubygems'
]
if @languages.include?("ruby")
if PlatformInfo.passenger_needs_ruby_dev_header?
ids << 'ruby-dev'
end
ids << 'rack'
end
return [specs, ids]
end
def install_doc_url
"https://www.phusionpassenger.com/library/install/nginx/"
end
def troubleshooting_doc_url
"https://www.phusionpassenger.com/library/admin/nginx/troubleshooting/"
end
def run_steps
# Make sure the configure script finds the correct
# passenger-config command.
ENV['PATH'] = PhusionPassenger.bin_dir + ":" + ENV['PATH']
show_welcome_screen
query_interested_languages
check_gem_install_permission_problems || exit(1)
check_directory_accessible_by_web_server
check_nginx_module_sources_available || exit(1)
check_dependencies || exit(1)
if needs_compiling_support_files?
check_whether_we_can_write_to(PhusionPassenger.build_system_dir) || exit(1)
end
check_whether_os_is_broken
check_whether_system_has_enough_ram
download_and_install = should_we_download_and_install_nginx_automatically?
if pcre_is_installed?
@pcre_source_dir = nil
else
@pcre_source_dir = download_and_extract_pcre
end
if download_and_install
nginx_source_dir = download_and_extract_nginx
if nginx_source_dir.nil?
show_possible_solutions_for_download_and_extraction_problems
exit(1)
end
nginx_prefix = ask_for_nginx_install_prefix
check_whether_other_nginx_installations_exist(nginx_prefix)
if @extra_configure_flags == "none"
extra_nginx_configure_flags = nil
else
extra_nginx_configure_flags = @extra_configure_flags
end
else
nginx_source_dir = ask_for_nginx_source_dir
nginx_prefix = ask_for_nginx_install_prefix
check_whether_other_nginx_installations_exist(nginx_prefix)
extra_nginx_configure_flags = ask_for_extra_nginx_configure_flags(nginx_prefix)
end
check_whether_we_can_write_to(nginx_prefix) || exit(1)
nginx_config_already_exists_before_installing = nginx_config_exists?(nginx_prefix)
if needs_compiling_support_files?
if !compile_passenger_support_files
show_possible_solutions_for_compilation_and_installation_problems
exit(1)
end
end
if install_nginx(nginx_source_dir, nginx_prefix, extra_nginx_configure_flags)
if nginx_config_already_exists_before_installing || !locate_nginx_config_file(nginx_prefix)
show_passenger_config_snippets(nginx_prefix)
else
insert_passenger_config_snippets(nginx_prefix)
end
show_deployment_example
else
show_possible_solutions_for_compilation_and_installation_problems
exit(1)
end
end
def before_install
super
myself = `whoami`.strip
@working_dir = PhusionPassenger::Utils.mktmpdir("passenger.", PlatformInfo.tmpexedir)
end
def after_install
super
FileUtils.remove_entry_secure(@working_dir) if @working_dir
end
private
def show_welcome_screen
render_template 'nginx/welcome', :version => VERSION_STRING
wait
end
def query_interested_languages
menu = TerminalChoiceMenu.new(["Ruby", "Python", "Node.js", "Meteor"])
menu["Ruby"].checked = interesting_language?('ruby')
menu["Python"].checked = interesting_language?('python')
menu["Node.js"].checked = interesting_language?('nodejs', 'node')
menu["Meteor"].checked = interesting_language?('meteor')
new_screen
puts "Which languages are you interested in?"
puts
if interactive?
puts "Use to select."
puts "If the menu doesn't display correctly, press '!'"
else
puts "Override selection with --languages."
end
puts
if interactive?
begin
menu.query
rescue Interrupt
raise Abort
end
else
menu.display_choices
puts
end
@languages = menu.selected_choices.map{ |x| x.downcase.gsub(/\./, '') }
end
def interesting_language?(name, command = nil)
if @languages
return @languages.include?(name)
else
return !!PlatformInfo.find_command(command || name)
end
end
def check_nginx_module_sources_available
if PhusionPassenger.custom_packaged? &&
(!PhusionPassenger.nginx_module_source_dir || !File.exist?(PhusionPassenger.nginx_module_source_dir))
new_screen
render_template 'nginx/nginx_module_sources_not_available'
return false
else
return true
end
end
def needs_compiling_support_files?
return !PhusionPassenger.build_system_dir.nil?
end
def compile_passenger_support_files
new_screen
puts "Compiling Passenger support files..."
Dir.chdir(PhusionPassenger.build_system_dir) do
return sh("env NOEXEC_DISABLE=1 #{PlatformInfo.rake_command} nginx:clean nginx RELEASE=yes")
end
end
def should_we_download_and_install_nginx_automatically?
new_screen
render_template 'nginx/query_download_and_install',
:nginx_version => PREFERRED_NGINX_VERSION
puts
if @auto_download
puts "=> Proceeding with choice 1."
return true
elsif @nginx_source_dir
puts "=> Proceeding with choice 2."
return false
elsif @auto
puts "=> Proceeding with choice 1."
return true
else
choice = prompt("Enter your choice (1 or 2) or press Ctrl-C to abort") do |input|
if input == "1" || input == "2"
true
elsif input.empty?
puts "No choice has been given."
false
else
puts "'#{input}' is not a valid choice."
false
end
end
return choice == "1"
end
end
def download_and_extract_pcre
new_screen
puts "PCRE (required by Nginx) not installed, downloading it..."
url = "https://github.com/PhilipHazel/pcre2/releases/download/pcre2-#{PREFERRED_PCRE_VERSION}/pcre2-#{PREFERRED_PCRE_VERSION}.tar.gz"
dirname = "pcre2-#{PREFERRED_PCRE_VERSION}"
tarball = "#{@working_dir}/pcre.tar.gz"
if download(url, tarball, DOWNLOAD_OPTION)
Dir.chdir(@working_dir) do
puts "Verifying PCRE checksum..."
if sha256_file(tarball) != PCRE_SHA256_CHECKSUM
new_screen
render_template "nginx/pcre_checksum_could_not_be_verified"
wait
return nil
end
puts "Extracting PCRE source tarball..."
if sh("tar", "xzvf", tarball)
return "#{@working_dir}/#{dirname}"
else
new_screen
render_template "nginx/pcre_could_not_be_extracted"
wait
return nil
end
end
else
new_screen
render_template "nginx/pcre_could_not_be_downloaded"
wait
return nil
end
rescue Interrupt
exit 2
end
def download_and_extract_nginx
new_screen
puts "Downloading Nginx..."
url = "https://nginx.org/download/nginx-#{PREFERRED_NGINX_VERSION}.tar.gz"
dirname = "nginx-#{PREFERRED_NGINX_VERSION}"
tarball = "#{@working_dir}/nginx.tar.gz"
if download(url, tarball, DOWNLOAD_OPTION)
Dir.chdir(@working_dir) do
puts "Verifying Nginx checksum..."
if sha256_file(tarball) != NGINX_SHA256_CHECKSUM
return nil
end
puts "Extracting Nginx source tarball..."
if sh("tar", "xzvf", tarball)
return "#{@working_dir}/#{dirname}"
else
return nil
end
end
else
return nil
end
rescue Interrupt
exit 2
end
def show_possible_solutions_for_download_and_extraction_problems
new_screen
render_template "nginx/possible_solutions_for_download_and_extraction_problems"
puts
end
def ask_for_nginx_install_prefix
new_screen
puts "Where do you want to install Nginx to?"
puts
if @prefix
puts "=> #{@prefix}"
return @prefix
elsif @auto
puts "=> /opt/nginx"
return "/opt/nginx"
else
prefix = prompt("Please specify a prefix directory [/opt/nginx]") do |input|
if input.empty? || input =~ %r(/)
true
else
puts "Please specify an absolute path."
false
end
end
if prefix.empty?
prefix = "/opt/nginx"
end
return prefix
end
end
def check_whether_other_nginx_installations_exist(nginx_prefix)
check_for = [
"/usr/bin/nginx",
"/usr/sbin/nginx"
]
existing_binary = nil
check_for.each do |filename|
if File.exist?(filename)
existing_binary = filename
break
end
end
if existing_binary
new_screen
render_template 'nginx/other_nginx_installations_exist',
:existing_binary => existing_binary,
:prefix => nginx_prefix
wait
end
end
def ask_for_nginx_source_dir
new_screen
puts "Where is your Nginx source code located?"
puts
if @nginx_source_dir
puts "=> #{@nginx_source_dir}"
return @nginx_source_dir
else
return prompt("Please specify the directory") do |input|
if input =~ %r(/)
if File.exist?("#{input}/src/core/nginx.c")
true
else
puts "'#{input}' does not look like an Nginx source directory."
false
end
else
puts "Please specify an absolute path."
false
end
end
end
end
def ask_for_extra_nginx_configure_flags(prefix)
done = false
while !done
new_screen
render_template 'nginx/ask_for_extra_configure_flags',
:command => build_nginx_configure_command(prefix)
puts
if @extra_configure_flags
if @extra_configure_flags == "none"
extra_args = ""
puts "=> No extra configure flags."
else
extra_args = @extra_configure_flags
puts "=> #{extra_args}"
end
return extra_args
elsif @auto
puts "=> No extra configure flags."
return ""
else
extra_args = prompt "Extra arguments to pass to configure script"
new_screen
render_template 'nginx/confirm_extra_configure_flags',
:command => build_nginx_configure_command(prefix, extra_args)
puts
answer = prompt("Is this what you want? (yes/no) [default=yes]") do |input|
if input.empty? || input == "yes" || input == "no"
true
else
puts "Please enter 'yes' or 'no'."
false
end
end
done = answer.empty? || answer == "yes"
end
end
return extra_args
end
def check_whether_we_can_write_to(dir)
FileUtils.mkdir_p(dir)
File.new("#{dir}/__test__.txt", "w").close
return true
rescue
new_screen
if Process.uid == 0
render_template 'nginx/cannot_write_to_dir', :dir => dir
else
render_template 'installer_common/run_installer_as_root',
:dir => dir,
:sudo => PhusionPassenger::PlatformInfo.ruby_sudo_command,
:sudo_s_e => PhusionPassenger::PlatformInfo.ruby_sudo_shell_command("-E"),
:ruby => PhusionPassenger::PlatformInfo.ruby_command,
:installer => "#{PhusionPassenger.bin_dir}/passenger-install-nginx-module #{ORIG_ARGV.join(' ')}"
end
return false
ensure
File.unlink("#{dir}/__test__.txt") rescue nil
end
def nginx_config_exists?(prefix)
return !!locate_nginx_config_file(prefix)
end
def install_nginx(source_dir, prefix, extra_configure_flags)
Dir.chdir(source_dir) do
new_screen
puts "Compiling and installing Nginx..."
if !sh(build_nginx_configure_command(prefix, extra_configure_flags)) ||
!sh("make") ||
!sh("make install")
return false
end
end
return true
rescue Interrupt
raise Aborted
end
def show_passenger_config_snippets(prefix)
new_screen
render_template 'nginx/config_snippets',
:config_file => locate_nginx_config_file(prefix),
:passenger_root => PhusionPassenger.install_spec,
:ruby => PlatformInfo.ruby_command
wait
end
def show_deployment_example
line
puts
render_template 'nginx/deployment_example',
:deployment_guide_url => "https://www.phusionpassenger.com/library/deploy/nginx/deploy/",
:phusion_website => PHUSION_WEBSITE,
:passenger_website => PASSENGER_WEBSITE
end
def show_possible_solutions_for_compilation_and_installation_problems
line
puts
render_template 'nginx/possible_solutions_for_compilation_and_installation_problems',
:support_url => SUPPORT_URL
end
def locate_nginx_config_file(prefix)
["#{prefix}/conf/nginx.conf", "#{prefix}/etc/nginx.conf"].each do |filename|
if File.exist?(filename)
return filename
end
end
return nil
end
def insert_passenger_config_snippets(prefix)
config_file = locate_nginx_config_file(prefix)
contents = File.read(config_file)
contents.sub!(/^http \{/,
"http {\n" <<
" passenger_root #{PhusionPassenger.install_spec};\n" <<
" passenger_ruby #{PlatformInfo.ruby_command};\n")
File.open(config_file, 'w') do |f|
f.write(contents)
end
new_screen
render_template 'nginx/config_snippets_inserted',
:config_file => config_file,
:passenger_root => PhusionPassenger.install_spec,
:ruby => PlatformInfo.ruby_command
wait
end
def build_nginx_configure_command(prefix, extra_configure_flags = nil)
extra_cflags = "-Wno-error #{PlatformInfo.openssl_extra_cflags}".strip
extra_ldflags = PlatformInfo.openssl_extra_ldflags
command = "sh ./configure --prefix='#{prefix}' "
command << "--with-http_ssl_module "
command << "--with-http_v2_module "
command << "--with-http_realip_module "
command << "--with-http_gzip_static_module "
command << "--with-http_stub_status_module "
command << "--with-http_addition_module "
command << "--with-cc-opt=#{Shellwords.escape extra_cflags} "
command << "--with-ld-opt=#{Shellwords.escape extra_ldflags} "
if @pcre_source_dir
command << "--with-pcre='#{@pcre_source_dir}' "
elsif !pcre_is_installed?
command << "--without-http_rewrite_module "
end
command << "--add-module='#{PhusionPassenger.nginx_module_source_dir}' #{extra_configure_flags}"
command.strip!
return command
end
def pcre_is_installed?
if @pcre_is_installed.nil?
Dir.mktmpdir do |safe_tmpdir|
@pcre_is_installed = begin
File.open("#{safe_tmpdir}/passenger-check.c", 'w') do |f|
f.puts("#include ")
end
Dir.chdir("#{safe_tmpdir}") do
# Nginx checks for PCRE in multiple places...
system("(gcc -I/usr/local/include -I/usr/include/pcre2 " <<
"-I/usr/pkg/include -I/opt/local/include " <<
"-I/opt/homebrew/include " <<
"-DPCRE2_CODE_UNIT_WIDTH=8 " <<
"-c passenger-check.c) >/dev/null 2>/dev/null")
end
ensure
File.unlink("#{safe_tmpdir}/passenger-check.c") rescue nil
File.unlink("#{safe_tmpdir}/passenger-check.o") rescue nil
end
end
end
return @pcre_is_installed
end
def sha256_file(path)
# We do this instead of using #file, for Ruby 1.8.5 support.
digest = Digest::SHA256.new
File.open(path, "rb") do |f|
buf = ''
buf.force_encoding('binary') if buf.respond_to?(:force_encoding)
while !f.eof?
f.read(1024 * 16, buf)
digest.update(buf)
end
end
return digest.hexdigest
end
end
ORIG_ARGV = ARGV.dup
options = {}
parser = OptionParser.new do |opts|
opts.banner = "Usage: passenger-install-nginx-module [options]"
opts.separator ""
indent = ' ' * 37
opts.separator "Options:"
opts.on("--auto", "Automatically confirm 'Press ENTER to\n" <<
"#{indent}continue' prompts.") do
options[:auto] = true
end
opts.on("--prefix=DIR", String, "Use the given Nginx install prefix instead\n" <<
"#{indent}of asking for it interactively.") do |dir|
options[:prefix] = dir
end
opts.on("--auto-download", "Download and install Nginx automatically,\n" <<
"#{indent}instead of asking interactively whether to\n" <<
"#{indent}download+install or to use an existing\n" <<
"#{indent}Nginx source directory.") do
options[:auto_download] = true
end
opts.on("--nginx-source-dir=DIR", String, "Compile and install Nginx using the given\n" <<
"#{indent}Nginx source directory, instead of\n" <<
"#{indent}interactively asking to download+install\n" <<
"#{indent}or to use an existing Nginx source\n" <<
"#{indent}directory. Conflicts with --auto-download.") do |dir|
options[:nginx_source_dir] = dir
end
opts.on("--extra-configure-flags=STRING", String, "Pass these extra flags to Nginx's\n" <<
"#{indent}'configure' script, instead of asking for\n" <<
"#{indent}it interactively. Specify 'none' if you\n" <<
"#{indent}do not want to pass additional flags but do\n" <<
"#{indent}not want this installer to ask\n" <<
"#{indent}interactively either.") do |flags|
options[:extra_configure_flags] = flags
end
opts.on("--languages NAMES", "Comma-separated list of interested\n" <<
"#{indent}languages (e.g.\n" <<
"#{indent}'ruby,python,nodejs,meteor')") do |value|
options[:languages] = value.split(",")
end
opts.on("--force-colors", "Display colors even if stdout is not a TTY") do
options[:colorize] = true
end
end
begin
parser.parse!
rescue OptionParser::ParseError => e
puts e
puts
puts "Please see '--help' for valid options."
exit 1
end
if options[:auto_download] && options[:nginx_source_dir]
STDERR.puts "You cannot specify both --auto-download and --nginx-source-dir."
exit 1
end
Installer.new(options).run