Friday, December 07, 2012

Ubuntu 12.04 Building your Own Local Ruby Gem Server using RBENV / NGINX Unicorn

There are times you would like to run your own gem server. You may want to share gems with colleagues when you are both without internet connectivity. You may have private code, internal to your organization, that you’d like to distribute and manage as gems without making the source publicly available. In this blog post, I've configured Ubuntu 12.04, RBENV, Nginx and Unicorn to serve Ruby Gems locally.

Install RBENV


git clone git://github.com/sstephenson/rbenv.git /usr/local/rbenv

# Add rbenv to the path:
echo '# rbenv setup' > /etc/profile.d/rbenv.sh
echo 'export RBENV_ROOT=/usr/local/rbenv' >> /etc/profile.d/rbenv.sh
echo 'export PATH="$RBENV_ROOT/bin:$PATH"' >> /etc/profile.d/rbenv.sh
echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh

chmod +x /etc/profile.d/rbenv.sh
source /etc/profile.d/rbenv.sh

# Install ruby-build:
pushd /tmp
git clone git://github.com/sstephenson/ruby-build.git
cd ruby-build
./install.sh
popd

# Install Ruby 1.9.3-p125:
rbenv install 1.9.3-p194
rbenv global 1.9.3-p194

# Rehash:
rbenv rehash

Nginx / Unicorn

add-apt-repository ppa:nginx/stable
apt-get update
apt-get -y install nginx git-core build-essential

gem install unicorn --no-rdoc --no-ri
rbenv rehash


Add the following environment config variables to a file at

/etc/unicorn/geminabox
RAILS_ROOT=/var/www/geminabox
RAILS_ENV=production

create app-specific unicorn init file here and make it executable

vim /etc/init.d/geminabox


chmod +x /etc/init.d/geminabox


/etc/init.d/geminabox start

sample init.d script

#! /bin/bash

### BEGIN INIT INFO
# Provides:          unicorn
# Required-Start:    $local_fs $remote_fs $network $syslog
# Required-Stop:     $local_fs $remote_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the unicorn web server
# Description:       starts unicorn
### END INIT INFO
APP=/var/www/geminabox/
USER=root
PATH=/usr/local/rbenv/bin:/usr/local/rbenv/shims:$PATH
DAEMON=unicorn
DAEMON_OPTS="-c $APP/config/unicorn.rb -E production -D"
NAME=unicorn
DESC="Unicorn app for $USER"
PID=/var/www/pids/unicorn.pid

case "$1" in
start)
     CD_TO_APP_DIR="cd $APP"
     START_DAEMON_PROCESS="bundle exec $DAEMON $DAEMON_OPTS"

     echo -n "Starting $DESC: "
     if [ `whoami` = root ]; then
       su - $USER -c "$CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS"
     else
       $CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS
     fi
     echo "$NAME."
     ;;
stop)
     echo -n "Stopping $DESC: "
     kill -QUIT `cat $PID`
     echo "$NAME."
     ;;
restart)
     echo -n "Restarting $DESC: "
     kill -USR2 `cat $PID`
     echo "$NAME."
     ;;
reload)
     echo -n "Reloading $DESC configuration: "
     kill -HUP `cat $PID`
     echo "$NAME."
     ;;
*)
     echo "Usage: $NAME {start|stop|restart|reload}" >&2
     exit 1
     ;;
esac

exit 0

Gemfile



gem "unicorn"

sample /var/www/geminabox/app/unicorn.rb


# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete documentation
#
# This file should go in the config directory of your Rails app e.g. config/unicorn.rb

app_dir = "/var/www/geminabox/"
worker_processes 10
working_directory app_dir

# Load app into the master before forking workers for super-fast
# worker spawn times
preload_app true

# nuke workers after 60 seconds (the default)
timeout 60

# listen on a Unix domain socket and/or a TCP port,

listen 8080 # listen to port 8080 on all TCP interfaces
#listen "127.0.0.1:8080"  # listen to port 8080 on the loopback interface
listen "/tmp/geminabox.socket"

# Don't set user if you are already running as the user (will cause a massive chown loop of death)
# This is for if you execute as root and become user.
#user 'example.co.uk', 'example.co.uk'

pid "/var/www/pids/unicorn.pid"
stderr_path "#{app_dir}/log/unicorn.stderr.log"
stdout_path "#{app_dir}/log/unicorn.stdout.log"

# http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
if GC.respond_to?(:copy_on_write_friendly=)
GC.copy_on_write_friendly = true
end


before_fork do |server, worker|
# the following is highly recomended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!

##
# When sent a USR2, Unicorn will suffix its pidfile with .oldbin and
# immediately start loading up a new version of itself (loaded with a new
# version of our app). When this new Unicorn is completely loaded
# it will begin spawning workers. The first worker spawned will check to
# see if an .oldbin pidfile exists. If so, this means we've just booted up
# a new Unicorn and need to tell the old one that it can now die. To do so
# we send it a QUIT.
#
# Using this method we get 0 downtime deploys.

old_pid = "#{server.config[:pid]}.oldbin"

if File.exists?(old_pid) && server.pid != old_pid
 begin
   sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
   Process.kill(sig, 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|
# Unicorn master loads the app then forks off workers - because of the way
# Unix forking works, we need to make sure we aren't using any of the parent's
# sockets, e.g. db connection

defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
# Redis and Memcached would go here but their connections are established
# on demand, so the master never opens a socket
end

sample /etc/nginx/sites-available/geminabox


upstream example-workers {
 # fail_timeout=0 means we always retry an upstream even if it failed
 # to return a good HTTP response (in case the Unicorn master nukes a single worker for timing out).
 server unix:/tmp/geminabox.socket fail_timeout=0;
}

server {
listen                80; # default;
server_name           www.testserver.com;
root                  /var/www/geminabox/;
client_max_body_size 10m;
location / {
 access_log          off;

 include proxy_params;
 proxy_redirect off;

 if (-f $request_filename) {
   access_log          off;
   expires             max;
   break;
 }

 if (-f $request_filename.html) {
   rewrite (.*) $1.html break;
 }

 if (!-f $request_filename) {
   proxy_pass          http://example-workers;
   break;
 }
}
}


Link the files in NGINX

ln -s /etc/nginx/sites-available/geminabox /etc/nginx/sites-enabled/geminabox


Start on Boot
update-rc.d geminabox defaults

Gem in a Box

https://github.com/cwninja/geminabox

cd /var
mkdir www
cd www
mkdir geminabox
cd geminabox
mkdir data


vim config.ru

require "rubygems"
require "geminabox"

Geminabox.data = "/var/www/geminabox/data" # ... or wherever
run Geminabox


vim Gemfile

gem "unicorn"
gem "geminabox"

No comments: