Skip to content

Instantly share code, notes, and snippets.

@ahoward
Created November 4, 2011 04:58
Show Gist options
  • Save ahoward/1338691 to your computer and use it in GitHub Desktop.
Save ahoward/1338691 to your computer and use it in GitHub Desktop.
how dojo4 is running apache -> unicorn | file ./script/unicorn
#! /usr/bin/env ruby
# file: RAILS_ROOT/script/unicorn
#
# this script has a runs a rails app under a specific rails_env and ruby
# version based on a local cap configuration. it has a few goals:
#
# - a single script is used to run servers for all of
# - local development
# - init.id on the remote server
# - remote cap start/restart
#
#
# - the script ensures
# - the right ruby version is being run
# - the right user:group is running the app
# - rolling (0 downtime) deploys
#
#
# - this script can load a Rails.root/config/unicorn.yml and fold in any
# environment settings. it is in this way that a specific rails_env, ruby
# version, or unix user/group can be specified for both init.d *and* cap
# deployments.
#
#
# - the script has a bit if ruby/unix magic in it
# - it can alternately be loaded as both a script or the script's config file
# - it will reexec with the correct ruby if the wrong one is run
#
#
# - the built-in unicorn configuration handles re-opening sockets for common
# O(RD)Ms and also ensures running the app as the configured user:group in the
# case of an init.d start
#
# it is probably worth noting that the unicorn cluster is fronted via apache
# or nginx. here is an apache config which says: proxy everything to unicorn
# *except* in the case of a static asset which may be accelerated.
#
# <VirtualHost *:80>
# PassengerEnabled off
# ServerName rails_app.dojo4.com
#
# DocumentRoot /ebs/apps/rails_app/current/public
#
# <Directory /ebs/apps/rails_app/current/public/>
# AllowOverride all
# Options -MultiViews
# </Directory>
#
# RewriteEngine On
#
# <Proxy balancer://unicornservers>
# BalancerMember http://0.0.0.0:4202 retry=0
# </Proxy>
#
# <Proxy *>
# Order deny,allow
# Allow from all
# </Proxy>
#
# RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
# RewriteRule ^/(.*)$ balancer://unicornservers%{REQUEST_URI} [P,QSA,L]
#
# ProxyPreserveHost on
#
# ErrorDocument 503 "Oh noes! Teh app is blown!"
#
# XSendFile On
# XSendFileAllowAbove on
# </VirtualHost>
#
#
INTERPRET_THIS_FILE_AS_A_SCRIPT = $0 == __FILE__
INTERPRET_THIS_FILE_AS_A_CONFIG = !INTERPRET_THIS_FILE_AS_A_SCRIPT
if INTERPRET_THIS_FILE_AS_A_SCRIPT
# setup
#
require 'fileutils'
require 'rbconfig'
require 'yaml'
require 'ostruct'
Unicorn = OpenStruct.new
THIS_SCRIPT = File.expand_path(__FILE__)
SCRIPT_DIR = File.dirname(THIS_SCRIPT)
RAILS_ROOT = File.dirname(SCRIPT_DIR)
# fold any settings from ./config/unicorn.yml into the environment. we can
# use these to force a particular RAILS_ENV setting, a particular ruby
# version, or a particular user for the application to run as. these
# settings will apply whether this script is called from init.d or via a cap
# deploy - this file would normally *not* be in revision control but
# symlink'd in via cap...
#
# example ./config/unicorn.yml
#
# RAILS_ENV : production
# RAILS_STAGE : staging
# UNICORN_RUBY : /usr/local/rbenv/versions/1.9.3-rc1/bin/ruby
# UNICORN_USER : dojo4
# UNICORN_GROUP : dojo4
#
require 'yaml' unless defined?(YAML)
config_yml = File.expand_path('../../config/unicorn.yml', __FILE__)
if test(?s, config_yml)
config = YAML.load(IO.read(config_yml))
if config.is_a?(Hash)
config.each do |key, val|
key, val = key.to_s, val.to_s
ENV[key] ||= val unless val.strip.empty?
end
end
end
# setup RAILS_ENV and RAILS_STAGE
#
# many of our applications accept compound RAILS_ENV settings such as
#
# production:staging
#
# meaning "run in production mode" but, for instance, do not process credit
# cards...
#
# we handle this here, and also bring RAILS_ENV and RAILS_STAGE into the
# codez
#
ENV['RAILS_ENV'] ||= 'development'
parts = ENV['RAILS_ENV'].to_s.split(%r/[._:-]+/)
if parts.size > 1
rails_env = parts.shift
rails_stage = parts.shift
ENV['RAILS_ENV'] = rails_env
ENV['RAILS_STAGE'] ||= rails_stage
abort('conflicting RAILS_STAGE setting') unless ENV['RAILS_STAGE'] == rails_stage
else
ENV['RAILS_STAGE'] ||= ENV['RAILS_ENV']
end
RAILS_ENV = ENV['RAILS_ENV']
RAILS_STAGE = ENV['RAILS_STAGE']
# we'll be using knowledge of the currently executing ruby for some
# cleverness below: mainly re-exec'ing if the wrong one is running...
#
rb = rb_config = ::RbConfig::CONFIG
THIS_RUBY = File.join(rb['bindir'], rb['ruby_install_name']) << rb['EXEEXT']
# configure teh unicorn via command line and potentially folded environment
#
mode = ARGV.first || 'restart'
daemon = ARGV.include?('-d') || ARGV.include?('--daemon') || (STDIN.tty? ? false : true)
Unicorn.mode = mode
Unicorn.daemon = daemon
Unicorn.rails_env = RAILS_ENV
Unicorn.rails_stage = RAILS_STAGE
Unicorn.rails_root = RAILS_ROOT
Unicorn.config_file = THIS_SCRIPT
Unicorn.pid_file = File.join(RAILS_ROOT, "tmp/pids/unicorn.pid")
Unicorn.port = (ENV['UNICORN_PORT'] ||= '3000')
Unicorn.ruby = (ENV['UNICORN_RUBY'] ||= THIS_RUBY)
Unicorn.user = (ENV['UNICORN_USER'] ||= Etc.getpwuid.name)
Unicorn.group = (ENV['UNICORN_GROUP'] ||= Etc.getgrgid.name)
# iff a particular ruby has been specified, and we are *not* running it,
# re-exec using the configured ruby to remedy the situation
#
if THIS_RUBY != Unicorn.ruby
warn("replacing sucky ruby #{ THIS_RUBY } with #{ Unicorn.ruby } ...")
argv = [Unicorn.ruby, script, *ARGV].join(' ')
exec(argv)
end
# ensure that ruby's bin path is inherited by the unicorn application for
# app 'system' and backtick hygiene.
#
bindir = rb_config['bindir']
path = ENV['PATH']
ENV['PATH'] = "#{ bindir }:#{ path }"
# let's run outta rails root - just for sanity
#
Dir.chdir(RAILS_ROOT)
# so. this is what we gonna do...
#
stop_command = "kill -QUIT `cat #{ Unicorn.pid_file }`"
start_command = "unicorn_rails -E #{ Unicorn.rails_env }:#{ Unicorn.rails_stage } -c #{ Unicorn.config_file } -p #{ Unicorn.port }"
restart_command = "kill -USR2 `cat #{ Unicorn.pid_file }`"
start_command << " -D"
#puts stop_command
#puts start_command
#puts restart_command
# dump pid iff asked
#
if Unicorn.mode == 'pid'
begin
puts IO.read(Unicorn.pid_file)
rescue
exit(1)
end
end
# clean up old unicorn iff needed
#
if Unicorn.mode == 'stop'
exec(stop_command)
end
# start a new one
#
if Unicorn.mode == 'start'
system(stop_command) if test(?s, Unicorn.pid_file)
FileUtils.touch(File.join(RAILS_ROOT, 'tmp', 'restart.txt'))
exec(start_command)
end
# (re)start
#
if Unicorn.mode =='restart'
FileUtils.touch(File.join(RAILS_ROOT, 'tmp', 'restart.txt'))
if test(?s, Unicorn.pid_file)
exec(restart_command)
else
exec(start_command)
end
end
end ### interpret_this_file_as_a_script
if INTERPRET_THIS_FILE_AS_A_CONFIG
rails_env = ENV['RAILS_ENV'] || 'development'
rails_root = File.dirname(File.dirname(__FILE__))
worker_processes (rails_env == 'production' ? 8 : 2)
preload_app true
timeout 30
#listen '/data/github/current/tmp/sockets/unicorn.sock', :backlog => 2048
if GC.respond_to?(:copy_on_write_friendly=)
GC.copy_on_write_friendly = true
end
unless STDIN.tty?
stderr_path rails_root + "/log/unicorn.stderr.log"
stdout_path rails_root + "/log/unicorn.stdout.log"
end
before_fork do |server, worker|
old_pid = rails_root + '/tmp/pids/unicorn.pid.oldbin'
if File.exists?(old_pid) && server.pid != old_pid
begin
Process.kill("QUIT", File.read(old_pid).to_i)
rescue Errno::ENOENT, Errno::ESRCH
# someone else did our job for us
end
end
end
after_fork do |server, worker|
Mongoid.reconnect! if defined?(Mongoid)
ActiveRecord::Base.establish_connection if defined?(ActiveRecord::Base)
# unicorn master might be started as root, which is fine, but let's drop the
# workers to the app's configured user/group
#
begin
uid, gid = Process.euid, Process.egid
user, group = ENV['UNICORN_USER'], ENV['UNICORN_GROUP']
if user and group
target_uid = Etc.getpwnam(user).uid
target_gid = Etc.getgrnam(group).gid
worker.tmp.chown(target_uid, target_gid)
if uid != target_uid || gid != target_gid
Process.initgroups(user, target_gid)
Process::GID.change_privilege(target_gid)
Process::UID.change_privilege(target_uid)
end
end
rescue Object => e
#if rails_env == 'development'
STDERR.puts "couldn't change user, oh well. shit might blow up l8r..."
#else
#raise e
#end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment