# Phusion Passenger - https://www.phusionpassenger.com/
# Copyright (c) 2010-2013 Phusion
#
# "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
#
# 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 'optparse'
require 'phusion_passenger'
require 'phusion_passenger/constants'
require 'phusion_passenger/standalone/utils'
module PhusionPassenger
module Standalone
class Command
DEFAULT_OPTIONS = {
:address => '0.0.0.0',
:port => 3000,
:environment => ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development',
:max_pool_size => 6,
:min_instances => 1,
:spawn_method => Kernel.respond_to?(:fork) ? 'smart' : 'direct',
:concurrency_model => DEFAULT_CONCURRENCY_MODEL,
:thread_count => DEFAULT_THREAD_COUNT,
:nginx_version => PREFERRED_NGINX_VERSION,
:friendly_error_pages => true
}.freeze
include Utils
def self.show_in_command_list
return true
end
def self.description
return nil
end
def initialize(args)
@args = args.dup
@original_args = args.dup
@options = DEFAULT_OPTIONS.dup
end
private
def require_daemon_controller
if !defined?(DaemonController)
begin
require 'daemon_controller'
begin
require 'daemon_controller/version'
too_old = DaemonController::VERSION_STRING < '1.1.0'
rescue LoadError
too_old = true
end
if too_old
error "Your version of daemon_controller is too old. " <<
"You must install 1.1.0 or later. Please upgrade:\n\n" <<
" sudo gem uninstall FooBarWidget-daemon_controller\n" <<
" sudo gem install daemon_controller"
exit 1
end
rescue LoadError
error "Please install daemon_controller first:\n\n" <<
" sudo gem install daemon_controller"
exit 1
end
end
end
def require_erb
require 'erb' unless defined?(ERB)
end
def require_optparse
require 'optparse' unless defined?(OptionParser)
end
def require_app_finder
require 'phusion_passenger/standalone/app_finder' unless defined?(AppFinder)
end
def debugging?
return ENV['PASSENGER_DEBUG'] && !ENV['PASSENGER_DEBUG'].empty?
end
def parse_options!(command_name, description = nil)
help = false
global_config_file = File.join(ENV['HOME'], USER_NAMESPACE_DIRNAME, "standalone", "config")
if File.exist?(global_config_file)
require 'phusion_passenger/standalone/config_file' unless defined?(ConfigFile)
global_options = ConfigFile.new(:global_config, global_config_file).options
@options.merge!(global_options)
end
require_optparse
parser = OptionParser.new do |opts|
opts.banner = "Usage: passenger #{command_name} [options]"
opts.separator description if description
opts.separator " "
opts.separator "Options:"
yield opts
opts.on("-h", "--help", "Show this help message") do
help = true
end
end
parser.parse!(@args)
if help
puts parser
exit 0
end
end
def error(message)
if message =~ /\n/
STDERR.puts("*** ERROR ***\n" << wrap_desc(message, 80, 0))
else
STDERR.puts(wrap_desc("*** ERROR: #{message}", 80, 0))
end
@plugin.call_hook(:error, message) if @plugin
end
# Word wrap the given option description text so that it is formatted
# nicely in the --help output.
def wrap_desc(description_text, max_width = 43, newline_prefix_size = 37)
line_prefix = "\n" << (' ' * newline_prefix_size)
result = description_text.gsub(/(.{1,#{max_width}})( +|$\n?)|(.{1,#{max_width}})/, "\\1\\3#{line_prefix}")
result.strip!
return result
end
def ensure_directory_exists(dir)
if !File.exist?(dir)
require_file_utils
FileUtils.mkdir_p(dir)
end
end
def determine_various_resource_locations(create_subdirs = true)
require_app_finder
if @options[:socket_file]
pid_basename = "passenger.pid"
log_basename = "passenger.log"
else
pid_basename = "passenger.#{@options[:port]}.pid"
log_basename = "passenger.#{@options[:port]}.log"
end
if @args.empty?
if AppFinder.looks_like_app_directory?(".")
@options[:pid_file] ||= File.expand_path("tmp/pids/#{pid_basename}")
@options[:log_file] ||= File.expand_path("log/#{log_basename}")
if create_subdirs
ensure_directory_exists(File.dirname(@options[:pid_file]))
ensure_directory_exists(File.dirname(@options[:log_file]))
end
else
@options[:pid_file] ||= File.expand_path(pid_basename)
@options[:log_file] ||= File.expand_path(log_basename)
end
else
@options[:pid_file] ||= File.expand_path(File.join(@args[0], pid_basename))
@options[:log_file] ||= File.expand_path(File.join(@args[0], log_basename))
end
end
def write_nginx_config_file
require 'phusion_passenger/platform_info/ruby'
require 'phusion_passenger/utils/tmpio'
@temp_dir = PhusionPassenger::Utils.mktmpdir(
"passenger-standalone.")
@config_filename = "#{@temp_dir}/config"
location_config_filename = "#{@temp_dir}/locations.ini"
File.chmod(0755, @temp_dir)
Dir.mkdir("#{@temp_dir}/logs")
locations_ini_fields =
PhusionPassenger::REQUIRED_LOCATIONS_INI_FIELDS +
PhusionPassenger::OPTIONAL_LOCATIONS_INI_FIELDS -
[:agents_dir, :lib_dir]
File.open(location_config_filename, 'w') do |f|
f.puts '[locations]'
f.puts "natively_packaged=false"
f.puts "lib_dir=#{@runtime_locator.find_lib_dir}"
f.puts "agents_dir=#{@runtime_locator.find_agents_dir}"
locations_ini_fields.each do |field|
value = PhusionPassenger.send(field)
f.puts "#{field}=#{value}" if value
end
end
puts File.read(location_config_filename) if debugging?
File.open(@config_filename, 'w') do |f|
f.chmod(0644)
template_filename = File.join(PhusionPassenger.resources_dir,
"templates", "standalone", "config.erb")
require_erb
erb = ERB.new(File.read(template_filename))
current_user = Etc.getpwuid(Process.uid).name
# The template requires some helper methods which are defined in start_command.rb.
output = erb.result(binding)
f.write(output)
puts output if debugging?
end
end
def serialize_strset(*items)
if "".respond_to?(:force_encoding)
items = items.map { |x| x.force_encoding('binary') }
null = "\0".force_encoding('binary')
else
null = "\0"
end
return [items.join(null)].pack('m*').gsub("\n", "").strip
end
def determine_nginx_start_command
if @options[:nginx_bin]
nginx_bin = @options[:nginx_bin]
else
nginx_bin = @runtime_locator.find_nginx_binary
end
return "#{nginx_bin} -c '#{@config_filename}' -p '#{@temp_dir}/'"
end
# Returns the port on which to ping Nginx.
def nginx_ping_port
if @options[:ping_port]
return @options[:ping_port]
else
return @options[:port]
end
end
def create_nginx_controller(extra_options = {})
require_daemon_controller
require 'socket' unless defined?(UNIXSocket)
require 'thread' unless defined?(Mutex)
if @options[:socket_file]
ping_spec = [:unix, @options[:socket_file]]
else
ping_spec = [:tcp, @options[:address], nginx_ping_port]
end
opts = {
:identifier => 'Nginx',
:before_start => method(:write_nginx_config_file),
:start_command => method(:determine_nginx_start_command),
:ping_command => ping_spec,
:pid_file => @options[:pid_file],
:log_file => @options[:log_file],
:timeout => 25
}
@nginx = DaemonController.new(opts.merge(extra_options))
@nginx_mutex = Mutex.new
end
end
end # module Standalone
end # module PhusionPassenger