Friday, October 12, 2012

Redmine Setup with NGINX/ Unicorn / RBENV Ubuntu 12.04 LTS

In the search for a good project management and issue tracking tool, we were looking at Jira. However, one of the big sore points of Jira was that it was written in Java. Since we are doing so much work with Ruby on Rails, I set out to look for a Rails based project management and issue tracking tool. One of the best open source tools we found recently is called Redmine. If you haven't heard of it, it's actually used quite a bit in the start up world.

The integration with Git is absolutely amazing - Redmine is definitely one of my favourite tools in the software development space. This document below shows how to setup Redmine in an optimal configuration using Ubuntu 12.04 LTS with Postgresql, Nginx, Unicorn and rbenv.


Installation

apt-get -y update
apt-get -y upgrade


apt-get install build-essential bison openssl curl git-core zlib1g zlib1g-dev screen libruby libcurl4-openssl-dev libssl-dev libmysqlclient-dev libxml2-dev   libxslt1-dev vim nodejs ssh libreadline-gplv2-dev automake -y

apt-get install python-software-properties -y
apt-get install libmagickwand-dev -y


apt-get install postgresql-server-dev-9.1 -y


PostgreSQL


sudo apt-get install postgresql-server-dev-9.1 postgresql -y

https://help.ubuntu.com/11.10/serverguide/C/postgresql.html

1) To connect to PostgreSQL from a remote machine modify the

vim /etc/postgresql/9.1/main/postgresql.conf

change the line

listen_addresses = 'localhost'

to

listen_addresses = '0.0.0.0'

exit the file

Change the default postgres user name

sudo -u postgres psql template1
ALTER USER postgres with encrypted password 'postgres';


2) modify the following file
vim /etc/postgresql/9.1/main/pg_hba.conf

Add the lines at the top of the file

local   all         all                         md5
host   all    all    10.100.0.0/16        md5


Start / Stop / Restart PostgreSQL

sudo /etc/init.d/postgresql restart



Option 1 - rubyenv

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


gem update
gem install rails

gem install pg
gem install execjs


gem install mysql2


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/redmine.test.com
RAILS_ROOT=/var/www/redmine-2.0
RAILS_ENV=production

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

vim /etc/init.d/redmine


chmod +x /etc/init.d/redmine


/etc/init.d/redmine 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/redmine-2.0/
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 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/redmine-2.0/"
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/redmine.test.com.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/redmine.test.com

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/redmine.test.com.socket fail_timeout=0;
}

server {
 listen                80; # default;
 server_name           redmine.test.com;
 root                  /var/www/redmine-2.0/public;
 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/redmine.test.com /etc/nginx/sites-enabled/redmine.test.com

Start on Boot
update-rc.d redmine defaults

References
https://gist.github.com/1303554
https://gist.github.com/1320707
Install Redmine

cd /var
mkdir www
cd www

git clone git://github.com/redmine/redmine.git

mv redmine redmine-2.0
cd redmine-2.0

bundle install --without development test rmagick sqlite


CREATE ROLE redmine LOGIN ENCRYPTED PASSWORD 'PASSWORD' NOINHERIT VALID UNTIL 'infinity';
CREATE DATABASE redmine WITH ENCODING='UTF8' OWNER=redmine;


Modify the database.yml

vim config/database.yml



production:
 adapter: postgresql
 database: redmine
 host: localhost
 username: redmine
 password: {Production password}
 encoding: utf8

rake generate_secret_token


RAILS_ENV=production rake db:migrate

RAILS_ENV=production rake redmine:load_default_data


Password is Admin / Admin

For Open ID
https://www.google.com/accounts/o8/id

System Administration, Backups and Restore

In our situation we are using Postgresql as the database and the files which are uploaded to /var/www/files

Mount the directory

vim /etc/fstab


//server.test.com/workstation-01-redmine /mnt/server/workstation-01-redmine smbfs username=backup,password={password} 0 0



restart the system

Rsync for Files - this will automatically archive all files to the backup directory

rsync -a --delete /var/www/redmine-2.0/files/ /mnt/server/workstation-01-redmine/files/

Make this a cron job so that it executes the sync every minute


Here is an example of the Crontab job /etc/crontab

# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
*/1 *   * * *   root    /var/scripts/rsyncfiles.sh
30 4,8,12,16,20,24      * * *   root    /var/scripts/workstation-01-redmine-backup.sh
# m h dom mon dow user  command
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )

Postgresql Backup and Restore

Create a password file so that the pg_dump will work without a password

vim ~/.pgpass

localhost:5432:redmine:postgres:postgres

chmod 0600 .pgpass


Backup
This will add the timestamp

pg_dump -U postgres -w > $(date +"%Y%m%d%H%M%S")_redmine.sql redmine

Restore
Create the database redmine then execute the command below

psql -U postgres -d redmine -f 20120520142447_redmine.sql



Openid
http://redmine.test.com

paste the following into openid URL

https://www.google.com/accounts/o8/id

then click Login and then create your username

2 comments:

Julian Caceres said...

OMG. is a amazing post. I will try it right now.
Regards.

Julian Caceres said...

OMG. is a amazing post. I will try it right now.
Regards.