# encoding: utf-8
#
# 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.
require 'fileutils'
require 'logger'
PhusionPassenger.require_passenger_lib 'constants'
PhusionPassenger.require_passenger_lib 'abstract_installer'
PhusionPassenger.require_passenger_lib 'common_library'
PhusionPassenger.require_passenger_lib 'config/installation_utils'
PhusionPassenger.require_passenger_lib 'platform_info'
PhusionPassenger.require_passenger_lib 'platform_info/ruby'
PhusionPassenger.require_passenger_lib 'platform_info/openssl'
PhusionPassenger.require_passenger_lib 'platform_info/compiler'
PhusionPassenger.require_passenger_lib 'utils/shellwords'
PhusionPassenger.require_passenger_lib 'utils/progress_bar'
PhusionPassenger.require_passenger_lib 'utils/tmpio'
module PhusionPassenger
module Config
class NginxEngineCompiler < AbstractInstaller
include InstallationUtils
def self.configure_script_options
extra_cflags = "-Wno-error #{PlatformInfo.openssl_extra_cflags}".strip
result = "--with-cc-opt=#{Shellwords.escape extra_cflags} "
extra_ldflags = PlatformInfo.openssl_extra_ldflags
if !extra_ldflags.empty?
result << "--with-ld-opt=#{Shellwords.escape extra_ldflags} "
end
result << "--without-http_fastcgi_module " \
"--without-http_scgi_module " \
"--without-http_uwsgi_module " \
"--with-ipv6 " \
"--with-http_ssl_module " \
"--with-http_v2_module " \
"--with-http_realip_module " \
"--with-http_gzip_static_module " \
"--with-http_stub_status_module " \
"--with-http_addition_module"
result
end
protected
def dependencies
specs = [
'depcheck_specs/compiler_toolchain',
'depcheck_specs/ruby',
'depcheck_specs/libs',
'depcheck_specs/utilities'
]
ids = [
'cc',
'c++',
'gmake',
'rake',
'openssl-dev',
'zlib-dev',
'pcre-dev'
].compact
return [specs, ids]
end
def install_doc_url
"https://www.phusionpassenger.com/library/install/standalone/"
end
def troubleshooting_doc_url
"https://www.phusionpassenger.com/library/admin/standalone/troubleshooting/"
end
def run_steps
check_source_code_available!
check_precompiled_support_libs_available!
if !@force
check_whether_os_is_broken
check_whether_system_has_enough_ram
check_for_download_tool!
end
check_dependencies(false) || abort
puts
@destdir = find_or_create_writable_support_binaries_dir!
puts "Installing..."
download_and_extract_nginx_sources
determine_support_libraries
if PhusionPassenger.build_system_dir
compile_support_libraries
end
configure_and_compile_nginx
end
def before_install
super
if !@working_dir
@working_dir = PhusionPassenger::Utils.mktmpdir("passenger-install.", PlatformInfo.tmpexedir)
@owns_working_dir = true
end
@nginx_version ||= PREFERRED_NGINX_VERSION
end
def after_install
super
FileUtils.remove_entry_secure(@working_dir) if @owns_working_dir
end
private
def check_source_code_available!
return if File.exist?(PhusionPassenger.nginx_module_source_dir)
if PhusionPassenger.originally_packaged?
puts "Broken #{PROGRAM_NAME} installation detected"
puts
puts "This program requires the #{PROGRAM_NAME} Nginx module sources " +
"before it can compile an Nginx engine. However, the #{PROGRAM_NAME} " +
"Nginx module sources are not installed, even though they should have been. " +
"This probably means that your #{PROGRAM_NAME} installation has somehow " +
"become corrupted. Please re-install #{PROGRAM_NAME}."
abort
else
case PhusionPassenger.packaging_method
when "deb"
command = "sudo sh -c 'apt-get update && apt-get install #{DEB_DEV_PACKAGE}'"
when "rpm"
command = "sudo yum install #{RPM_DEV_PACKAGE}-#{VERSION_STRING}"
end
if command
if STDIN.tty?
puts " --> Installing #{PROGRAM_NAME} Nginx module sources"
puts " Running: #{command}"
if !system(command)
puts " *** Command failed: #{command}"
abort
end
else
puts " --> #{PROGRAM_NAME} Nginx module sources not installed"
puts " Please install them first: #{command}"
abort
end
else
puts " --> #{PROGRAM_NAME} Nginx module sources not installed"
puts " Please ask your #{PROGRAM_NAME} packager or operating " +
"system vendor how to install these."
abort
end
end
end
def check_precompiled_support_libs_available!
return if PhusionPassenger.build_system_dir ||
File.exist?("#{PhusionPassenger.lib_dir}/common/libboost_oxt.a")
if PhusionPassenger.originally_packaged?
puts "Broken #{PROGRAM_NAME} installation detected"
puts
puts "This program requires the #{PROGRAM_NAME} support libraries " +
"before it can compile an Nginx engine. However, the #{PROGRAM_NAME} " +
"support libraries are not installed, even though they should have been. " +
"This probably means that your #{PROGRAM_NAME} installation has somehow " +
"become corrupted. Please re-install #{PROGRAM_NAME}."
abort
else
case PhusionPassenger.packaging_method
when "deb"
command = "sudo sh -c 'apt-get update && apt-get install #{DEB_DEV_PACKAGE}'"
when "rpm"
command = "sudo yum install #{RPM_DEV_PACKAGE}-#{VERSION_STRING}"
end
if command
if STDIN.tty?
puts " --> Installing #{PROGRAM_NAME} support libraries"
puts " Running: #{command}"
if !system(command)
puts " *** Command failed: #{command}"
abort
end
else
puts " --> #{PROGRAM_NAME} support libraries not installed"
puts " Please install them first: #{command}"
abort
end
else
puts " --> #{PROGRAM_NAME} support libraries not installed"
puts " Please ask your #{PROGRAM_NAME} packager or operating " +
"system vendor how to install these."
abort
end
end
end
def download_and_extract_nginx_sources
if @nginx_tarball
tarball = @nginx_tarball
else
puts "Downloading Nginx #{@nginx_version} source code..."
basename = "nginx-#{@nginx_version}.tar.gz"
tarball = "#{@working_dir}/#{basename}"
options = {
:show_progress => @stdout.tty?
}
if @connect_timeout && @connect_timeout != 0
options[:connect_timeout] = @connect_timeout
end
if @idle_timeout && @idle_timeout != 0
options[:idle_timeout] = @idle_timeout
end
result = download("http://nginx.org/download/#{basename}",
tarball, options)
if !result
puts
show_possible_solutions_for_download_and_extraction_problems
abort
end
end
puts "Extracting tarball..."
e_working_dir = Shellwords.escape(@working_dir)
e_tarball = Shellwords.escape(tarball)
result = system("cd #{e_working_dir} && tar xzf #{e_tarball}")
if !result
puts
if @nginx_tarball
new_screen
puts "You specified --nginx-tarball, but the file could not be extracted. " +
"Please check the path and format (tar.gz), and ensure Passenger can write to " +
PlatformInfo.tmpexedir + "."
puts
else
show_possible_solutions_for_download_and_extraction_problems
end
abort
end
end
def show_possible_solutions_for_download_and_extraction_problems
new_screen
render_template "nginx_engine_compiler/possible_solutions_for_download_and_extraction_problems"
puts
end
def determine_support_libraries
if PhusionPassenger.build_system_dir
lib_dir = "#{@working_dir}/common/libpassenger_common"
@support_libs = COMMON_LIBRARY.only(*NGINX_LIBS_SELECTOR).
set_output_dir(lib_dir).
link_objects
@support_libs << "#{@working_dir}/common/libboost_oxt.a"
else
@support_libs = COMMON_LIBRARY.only(*NGINX_LIBS_SELECTOR).
set_output_dir("#{PhusionPassenger.lib_dir}/common/libpassenger_common").
link_objects
@support_libs << "#{PhusionPassenger.lib_dir}/common/libboost_oxt.a"
end
@support_libs_string = @support_libs.join(" ")
end
def compile_support_libraries
puts "Compiling support libraries (step 1 of 2)..."
progress_bar = ProgressBar.new
e_working_dir = Shellwords.escape(@working_dir)
args = "#{@support_libs_string} CACHING=false OUTPUT_DIR=#{e_working_dir}"
begin
progress_bar.set(0.05)
Dir.chdir(PhusionPassenger.build_system_dir) do
run_rake_task!(args) do |progress, total|
progress_bar.set(0.05 + (progress / total.to_f) * 0.95)
end
end
progress_bar.set(1)
ensure
progress_bar.finish
end
end
def configure_and_compile_nginx
puts "Compiling Nginx engine (step 2 of 2)..."
progress_bar = ProgressBar.new
progress_bar.set(0)
begin
configure_nginx do
progress_bar.set(0.25)
end
compile_nginx do |progress|
progress_bar.set(0.25 + progress * 0.75)
end
progress_bar.set(1)
ensure
progress_bar.finish
end
FileUtils.cp("#{@working_dir}/nginx-#{@nginx_version}/objs/nginx",
"#{@destdir}/nginx-#{@nginx_version}")
puts "Compilation finished!"
end
def configure_nginx
shell = PlatformInfo.find_command('bash') || "sh"
e_nginx_source_dir = Shellwords.escape("#{@working_dir}/nginx-#{@nginx_version}")
command = "cd #{e_nginx_source_dir} && "
command << "env PASSENGER_INCLUDEDIR=#{Shellwords.escape PhusionPassenger.include_dir} " <<
"PASSENGER_LIBS=#{Shellwords.escape(@support_libs_string)} "
# RPM thinks it's being smart by scanning binaries for
# paths and refusing to create package if it detects any
# hardcoded thats that point to /usr or other important
# locations. For Phusion Passenger Standalone we do not
# care at all what the Nginx configured prefix is because
# we pass it its resource locations during runtime, so
# work around the problem by configure Nginx with prefix
# /tmp.
command << "#{shell} ./configure --prefix=/tmp " +
"#{self.class.configure_script_options} " +
"--add-module=#{Shellwords.escape PhusionPassenger.nginx_module_source_dir}"
run_command_yield_activity(command) do
yield
end
end
def compile_nginx
backlog = ""
e_nginx_source_dir = Shellwords.escape("#{@working_dir}/nginx-#{@nginx_version}")
# Capture and index the `make --dry-run` output for
# progress determination.
total_lines = 0
dry_run_output = {}
`cd #{e_nginx_source_dir} && #{PlatformInfo.gnu_make} --dry-run`.split("\n").each do |line|
total_lines += 1
dry_run_output[line] = true
end
IO.popen("cd #{e_nginx_source_dir} && #{PlatformInfo.gnu_make} 2>&1", "r") do |io|
progress = 1
while !io.eof?
line = io.readline
backlog << line
# If the output is part of what we saw when dry-running,
# then increase progress bar. Otherwise it could be compiler
# warnings or something, so ignore those.
if dry_run_output[line.chomp]
yield(progress / total_lines.to_f)
progress += 1
end
end
end
if $?.exitstatus != 0
@stderr.puts
@stderr.puts "*** ERROR: unable to compile web helper."
@stderr.puts backlog
exit 1
end
end
def run_command_yield_activity(command)
backlog = ""
IO.popen("#{command} 2>&1", "rb") do |io|
while !io.eof?
backlog << io.readline
yield
end
end
if $?.exitstatus != 0
@stderr.puts
@stderr.puts backlog
@stderr.puts "*** ERROR: command failed: #{command}"
exit 1
end
end
end
end # module Standalone
end # module PhusionPassenger