rrichards
2/12/2013 - 3:24 PM

0. nginx_setup.sh

# download and build nginx from sources
https://gist.github.com/3083579
# nginx upstart config 
https://gist.github.com/2024203
# nginx.conf

user                          nginx;
worker_processes              8;
worker_priority               -5;

error_log                     logs/nginx.error.log  crit;

events {
    use                       epoll;
    worker_connections        8192;
}

timer_resolution              500ms;

http {
    include                   mime.types;
    default_type              application/octet-stream;

    server_name_in_redirect off;
    server_tokens           off;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    sendfile                  on;
    tcp_nopush                on;
    keepalive_timeout         10;
    tcp_nodelay               on;

    client_max_body_size      1m;
    client_body_buffer_size   128k;
    client_body_timeout       30;
    client_body_temp_path     /tmp/client_body_temp;

    client_header_timeout     30;
    client_header_buffer_size 128;

    gzip                      on;
    gzip_http_version         1.1;
    gzip_disable              "msie6";
    gzip_vary                 on;
    gzip_min_length           1100;
    gzip_buffers              64 8k;
    gzip_comp_level           3;
    gzip_proxied              expired no-cache no-store private auth;    
    gzip_types                text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;
    
    ssl_certificate           /etc/nginx/ssl_certs/server.crt;
    ssl_certificate_key       /etc/nginx/ssl_certs/server.key;
    ssl_session_timeout       15m;
    ssl_protocols             SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers               RC4:HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache         shared:SSL:10m;

    add_header                Strict-Transport-Security "max-age=16070400; includeSubdomains";
    add_header                X-Frame-Options DENY;

    limit_req_zone            $binary_remote_addr zone=one:10m rate=10r/s;

    include                   /etc/nginx/sites-enabled/*;
}
# Nginx server block configuration with proxy_pass to Unicorn upstream
# We use full-SSL site with web-server redirection, no mess with Rails application redirection
#
# config/server/production/nginx_host.conf


upstream unicorn {
  server              unix:/tmp/unicorn.production.sock fail_timeout=0;
}


server {
  listen              80;
  server_name         server.com;
  rewrite             ^(.*) https://$host$1 permanent;

  location ~ \.(php|html)$ {
    deny              all;
  }

  access_log          /dev/null;
  error_log           /dev/null;
}


server {
  ssl                 on;
  listen              443 ssl;
  server_name         server.com;
  root                /home/app/public_html/app_production/current/public;
  try_files           $uri /system/maintenance.html @unicorn;

  location @unicorn {
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header  X-Forwarded-Proto $scheme;
    proxy_set_header  Host $http_host;
    proxy_redirect    off;
    proxy_pass        http://unicorn;
    limit_req         zone=one;

    access_log        /dev/null;
    error_log         logs/unicorn.error.log;
  }

  location ~ ^/(assets|images|javascripts|stylesheets|swfs|system)/ {
    gzip_static       on;
    expires           max;
    add_header        Cache-Control public;
    add_header        Last-Modified "";
    add_header        ETag "";
    break;
  }

  include             /home/app/public_html/app_production/current/config/server/production/nginx_errors.conf;

  access_log          /dev/null;
  error_log           /dev/null;
}
# nginx configuration piece to handle errorrs
#
# config/server/production/nginx_errors.conf

error_page          500 502 504  /500.html;
error_page          503 @503;

location = /50x.html {
    root            html;
}

location = /404.html {
    root            html;
}

location @503 {
  error_page 405 = /system/maintenance.html;
  if (-f $document_root/system/maintenance.html) {
    rewrite         ^(.*)$ /system/maintenance.html break;
  }
  rewrite           ^(.*)$ /503.html break;
}

if ($request_method !~ ^(GET|HEAD|PUT|POST|DELETE|OPTIONS)$ ){
  return            405;
}

if (-f $document_root/system/maintenance.html) {
  return            503;
}

location ~ \.(php|html)$ {
  return            405;
}
# Capistrano configuration
# 
# require 'new_relic/recipes'         - Newrelic notification about deployment
# require 'capistrano/ext/multistage' - We use 2 deployment environment: staging and production.
# set :deploy_via, :remote_cache      - fetch only latest changes during deployment
# set :normalize_asset_timestamps     - no need to touch (date modification) every assets
# "deploy:web:disable"                - traditional maintenance page (during DB migrations deployment)
# task :restart                       - Unicorn with preload_app should be reloaded by USR2+QUIT signals, not HUP
#
# http://unicorn.bogomips.org/SIGNALS.html
# "If “preload_app” is true, then application code changes will have no effect; 
# USR2 + QUIT (see below) must be used to load newer code in this case"
# 
# config/deploy.rb


require 'bundler/capistrano'
require 'capistrano/ext/multistage'
require 'new_relic/recipes'

set :stages,                     %w(staging production)
set :default_stage,              "staging"

set :scm,                        :git
set :repository,                 "..."
set :deploy_via,                 :remote_cache
default_run_options[:pty]        = true

set :application,                "app"
set :use_sudo,                   false
set :user,                       "app"
set :normalize_asset_timestamps, false


# Optional
before "deploy",                 "deploy:web:disable"
before "deploy:stop",            "deploy:web:disable"

after  "deploy:update_code",     "deploy:symlink_shared"

# Optional
after  "deploy:start",           "deploy:web:enable"
after  "deploy",                 "deploy:web:enable"

after  "deploy",                 "deploy:cleanup"


namespace :deploy do

  %w[start stop].each do |command|
    desc "#{command} unicorn server"
    task command, :roles => :app, :except => { :no_release => true } do
      run "#{current_path}/config/server/#{rails_env}/unicorn_init.sh #{command}"
    end
  end

  desc "restart unicorn server"
  task :restart, :roles => :app, :except => { :no_release => true } do
    run "#{current_path}/config/server/#{rails_env}/unicorn_init.sh upgrade"
  end


  desc "Link in the production database.yml and assets"
  task :symlink_shared do
    run "ln -nfs #{deploy_to}/shared/config/database.yml #{release_path}/config/database.yml"
  end


  namespace :web do
    desc "Maintenance start"
    task :disable, :roles => :web do
      on_rollback { run "rm #{shared_path}/system/maintenance.html" }
      page = File.read("public/503.html")
      put page, "#{shared_path}/system/maintenance.html", :mode => 0644
    end
    
    desc "Maintenance stop"
    task :enable, :roles => :web do
      run "rm #{shared_path}/system/maintenance.html"
    end
  end

end


namespace :log do
  desc "A pinch of tail"
  task :tailf, :roles => :app do
    run "tail -n 10000 -f #{shared_path}/log/#{rails_env}.log" do |channel, stream, data|
      puts "#{data}"
      break if stream == :err
    end
  end
end
# capistrano production config
#
# config/deploy/production.rb


server "8.8.8.8",                :app, :web, :db, :primary => true
set :branch,                     "production"
set :deploy_to,                  "/home/app/public_html/app_production"
set :rails_env,                  "production"
# Unicorn configuration file to be running by unicorn_init.sh with capistrano task
# read an example configuration before: http://unicorn.bogomips.org/examples/unicorn.conf.rb
# 
# working_directory, pid, paths - internal Unicorn variables must to setup
# worker_process 4              - is good enough for serve small production application
# timeout 30                    - time limit when unresponded workers to restart
# preload_app true              - the most interesting option that confuse a lot of us,
#                                 just setup is as true always, it means extra work on 
#                                 deployment scripts to make it correctly
# BUNDLE_GEMFILE                - make Gemfile accessible with new master
# before_fork, after_fork       - reconnect to all dependent services: DB, Redis, Sphinx etc.
#                                 deal with old_pid only if CPU or RAM are limited enough
#
# config/server/production/unicorn.rb


app_path          = "/home/app/public_html/app_production/current"

working_directory "#{app_path}"
pid               "#{app_path}/tmp/pids/unicorn.pid"
stderr_path       "#{app_path}/log/unicorn.log"
stdout_path       "#{app_path}/log/unicorn.log"

listen            "/tmp/unicorn.production.sock"
worker_processes  4
timeout           30
preload_app       true


before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = "#{app_path}/Gemfile"
end


before_fork do |server, worker|
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end

  if defined?(Resque)
    Resque.redis.quit
  end

  sleep 1
end


after_fork do |server, worker|
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end
  
  if defined?(Resque)
    Resque.redis           = 'localhost:6379'
  end
end
# Unicorn handle shell script
# 
# APP_ROOT, PID             - are the same as you setup above
# CMD                       - use bundle binstubs (bundle install --binstubs) to 
#                             forget about "bundle exec" stuff, run in demonize mode
#                             bin/unicorn is for Rack application (config.ru in root dir), but 
#                             bin/unicorn_rails is to use with Rails 2.3
#
# To handle "app_preload true" configuration we should use USR2+QUIT signals, not HUP!
# So we rewrite capistrano deployment scripts to manage it.
#
# config/server/production/unicorn_init.sh


#!/bin/sh
set -e
# Example init script, this can be used with nginx, too,
# since nginx and unicorn accept the same signals

TIMEOUT=${TIMEOUT-60}
APP_ROOT=/home/app/public_html/app_production/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="$APP_ROOT/bin/unicorn -D -c $APP_ROOT/config/server/unicorn.rb -E production"
action="$1"
set -u

old_pid="$PID.oldbin"

cd $APP_ROOT || exit 1

sig () {
        test -s "$PID" && kill -$1 `cat $PID`
}

oldsig () {
        test -s $old_pid && kill -$1 `cat $old_pid`
}

case $action in
start)
        sig 0 && echo >&2 "Already running" && exit 0
        $CMD
        ;;
stop)
        sig QUIT && exit 0
        echo >&2 "Not running"
        ;;
force-stop)
        sig TERM && exit 0
        echo >&2 "Not running"
        ;;
restart|reload)
        sig HUP && echo reloaded OK && exit 0
        echo >&2 "Couldn't reload, starting '$CMD' instead"
        $CMD
        ;;
upgrade)
        if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
        then
                n=$TIMEOUT
                while test -s $old_pid && test $n -ge 0
                do
                        printf '.' && sleep 1 && n=$(( $n - 1 ))
                done
                echo

                if test $n -lt 0 && test -s $old_pid
                then
                        echo >&2 "$old_pid still exists after $TIMEOUT seconds"
                        exit 1
                fi
                exit 0
        fi
        echo >&2 "Couldn't upgrade, starting '$CMD' instead"
        $CMD
        ;;
reopen-logs)
        sig USR1
        ;;
*)
        echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
        exit 1
        ;;
esac
# deploy speed up via SSH ControlMaster
#

$ mkdir ~/.ssh/cm_socket
$ vim ~/.ssh/config

  Host *
    ControlMaster auto
    ControlPath ~/.ssh/cm_socket/%r@%h:%p

$ cd ~/app
$ cap deploy