Redmine on unicorn+nginx Chef cookbook


redmineをunicorn+nginxで動かすためのchef用のCookbookです。<http://t.nzk.me/entry/2012/12/28/144730>で紹介されている手順を元に作成しています。

## 前提条件

* OS: Ubuntu 12.04
* DB: mysql

    mysqlについては、chefにより自動的なインストールが可能ですが、このcookbookでは、mysqlパッケージ自体のインストールは含んでいません。mysqlのインストールについては、別のcookbookを使用する必要があります。
* Redmine: 2.3.1
* chef-solo

## 変更が必要なファイル

**chef-repo/nodes/redmine.example.com.json**を環境に合わせて編集してください。

### download-urlについて

redmineパッケージをダウンロードするURLを指定します。

### run_listについて

**mysql::server**については、このcookbookには含まれていません。以下のコマンドで事前にmysqlインストール用のcookbookを導入してください。

```shell
knife cookbook site install mysql
```

## 実行手順

chefを実行する端末から、OSインストール直後のサーバ(sshで接続出来る必要はあります)に対してredmineをインストールする場合、以下のコマンドでredmineの起動可能な状態まで自動的に設定されます(たぶん)。

```shell
knife solo prepare redmine.exampl.ecom
knife solo cook redmine.example.com
```

nginxとunicornの起動は以下の通りです。

```shell
sudo /etc/init.d/unicorn start
sudo /etc/init.d/nginx start
```

## 関連ファイル

* chef-repo/nodes/redmine.example.com.json

    インストールするホスト毎の設定ファイルです。

* chef-repo/cookbooks/redmine-unicorn-nginx/recipes/default.rb

    redmineパッケージをダウンロードし、インストールするためのレシピです。必要なパッケージも自動的に取得します。unicorn及びnginxのインストール、設定も行いますが、mysqlパッケージのインストールは含まれていないため、事前に別のcookbookを用意する必要があります。
  
* chef-repo/cookbooks/redmine-unicorn-nginx/templates/default/database.yml.erb

    redmineがデータベースに接続するための設定ファイルです。mysqlを前提としています。

* chef-repo/cookbooks/redmine-unicorn-nginx/templates/default/initd_unicorn.erb

    uncornを起動するための設定ファイルです。<http://www.mk-mode.com/octopress/2013/01/23/unicorn-start-script/>を参考にさせて頂きました。

* chef-repo/cookbooks/redmine-unicorn-nginx/templates/default/nginx.conf.erb

    nginxの設定ファイルです。

* chef-repo/cookbooks/redmine-unicorn-nginx/templates/default/virtual.vconf.erb

    nginxの設定ファイルです。

* chef-repo/cookbooks/redmine-unicorn-nginx/templates/default/unicorn.rb.erb

    unicornの設定ファイルです。

```json:/chef-repo/nodes/redmine.example.com.json
{
    "mysql": {
        "server_root_password": "password",
        "server_repl_password": "password",
        "server_debian_password": "password"
    },
    "redmine": {
        "db_user": "redmine",
        "db_server": "localhost",
        "db_password": "password",
        "download-url": "http://rubyforge.org/frs/download.php/76933/redmine-2.3.1.tar.gz"
    },
    "nginx": {
        "server_name": "redmine.example.com"
    },
    "run_list": [
        "mysql::server",
        "mysql::client",
        "redmine-unicorn-nginx"
    ]
}
```

```ruby:chef-repo/cookbooks/redmine-unicorn-nginx/recipes/default.rb
# -*- coding: utf-8 -*-

execute "echo \"CREATE DATABASE redmine character set utf8\" | mysql -u root -p#{node['mysql']['server_root_password']}" do
  returns [0, 1]
end
execute "echo \"CREATE USER '#{node['redmine']['db_user']}'@'#{node['redmine']['db_server']}' IDENTIFIED BY '#{node['redmine']['db_password']}'\" | mysql -u root -p#{node['mysql']['server_root_password']}" do
  returns [0, 1]
end

execute "echo \"GRANT ALL PRIVILEGES ON redmine.* to #{node['redmine']['db_user']}\" | mysql -u root -p#{node['mysql']['server_root_password']}"

# ruby 1.9.3で動作させる場合の設定です。必須ではありません。
#apt_package "ruby1.9.3" do
#  action :install
#end

#execute "" do
#  command <<-EOH
#update-alternatives --install /usr/bin/ruby ruby /usr/bin/ruby1.9.3 500 \
#                    --slave   /usr/bin/ri   ri   /usr/bin/ri1.9.3 \
#                    --slave   /usr/bin/irb  irb  /usr/bin/irb1.9.3 \
#                    --slave   /usr/bin/erb  erb  /usr/bin/erb1.9.3 \
#                    --slave   /usr/bin/rdoc rdoc /usr/bin/rdoc1.9.3
#  EOH
#end

#execute "update-alternatives --set ruby /usr/bin/ruby1.9.3"

base_name = File.basename(node['redmine']['download-url'])

remote_file "redmine-archive" do
  source "#{node['redmine']['download-url']}"
  path "/usr/local/#{base_name}"
  not_if do
    File.exists?("/usr/local/#{base_name}")
  end
end

execute "tar zxvf #{base_name}" do
  cwd "/usr/local"
end

dir_name = base_name.gsub(/^(.+?)(\.tar\.gz)|(\.zip)$/, "\\1")

link "/usr/local/redmine" do
  to "/usr/local/#{dir_name}"
end

template "database.yml" do
  path "/usr/local/redmine/config/database.yml"
end

apt_package "make" do
  action :install
end

apt_package "ruby-dev" do
  action :install
end

apt_package "rake" do
  action :install
end

apt_package "libxml2-dev" do
  action :install
end

apt_package "libxslt-dev" do
  action :install
end

apt_package "libmagickwand-dev" do
  action :install
end

#apt_package "graphicsmagick-libmagick-dev-compat" do
#  action :install
#end

execute "gem install bundler" do
  cwd "/usr/local/redmine"
end

execute "bundle install" do
  cwd "/usr/local/redmine"
end

execute "bundle check" do
  cwd "/usr/local/redmine"
end

execute "rake generate_secret_token" do
  cwd "/usr/local/redmine"
end

execute "rake db:migrate" do
  environment(
              "RAILS_ENV" => "production"
              )
  cwd "/usr/local/redmine"
end

directory "/usr/local/redmine/public/plugin_asserts" do
  mode 777
end

execute "gem install unicorn"

# /usr/local/redmine/Gemfileにgem "unicorn"の記述が無いと、unicornの起動に失敗するため、gem "unicorn"の記述を追加します。
ruby "adding unicron text into redmine/Gemfile." do
  code <<-EOC
f = open("/usr/local/redmine/Gemfile", "r")
t = open("/tmp/redmine-uniconr-nginx-Gemfile.new", "w")

if f.grep(/^gem \"unicorn\"/).empty? then
  f.seek 0
  f.each do |line|
    if line =~ /^source .+/ then
      t.write line
      t.write "gem 'unicorn'\n"
    else
      t.write line
    end
  end
end

f.close
t.close
  EOC
end

execute "mv /tmp/redmine-uniconr-nginx-Gemfile.new /usr/local/redmine/Gemfile"

template "unicorn.rb" do
  path "/usr/local/redmine/config/unicorn.rb"
end

apt_package "nginx" do
  action :install
end

template "nginx.conf" do
  path "/etc/nginx/nginx.conf"
end

template "virtual.vconf" do
  path "/etc/nginx/conf.d/virtual.conf"
end

template "initd_unicorn" do
  path "/etc/init.d/unicorn"
  mode 755
end
```

```ruby:chef-repo/cookbooks/redmine-unicorn-nginx/templates/default/initd_unicorn.erb
#!/bin/sh

PATH=/bin:/usr/bin:/usr/local/bin

# move to project root directory
NAME=unicorn
ENVIROMENT=production

ROOT_DIR=/usr/local/redmine

PID="${ROOT_DIR}/tmp/pids/unicorn.pid"
CONF="${ROOT_DIR}/config/unicorn.rb"

start()
{
    cd $ROOT_DIR
    if [ -e $PID ]; then
        echo "$NAME already started";
        exit 1;
    fi
    echo "start $NAME";
    bundle exec unicorn_rails -c ${CONF} -E ${ENVIROMENT} -D -p 5001
}

stop()
{
    if [ ! -e $PID ]; then
        echo "$NAME not started";
        exit 1;
    fi
    echo "stop $NAME";
    kill `cat ${PID}`
    rm -f $PID
}

graceful_stop()
{
    if [ ! -e $PID ]; then
        echo "$NAME not started";
        exit 1;
    fi
    echo "stop $NAME";
    kill -QUIT `cat ${PID}`
    rm -f $PID
}

reload()
{
    if [ ! -e $PID ]; then
        echo "$NAME not started";
        start
        exit 0;
    fi
    echo "reload $NAME";
    kill -USR2 `cat ${PID}`
}

restart()
{
    if [ ! -e $PID ]; then
        echo "$NAME not started";
        start
        exit 0;
    fi
    echo "restart $NAME";
    stop
    start
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    graceful_stop)
        graceful_stop
        ;;
    reload)
        reload
        ;;
    restart)
        reload
        ;;
    *)
        echo "Syntax Error: release [start|stop|graceful_stop|reload|restart]"
        ;;
esac
```

```ruby:chef-repo/cookbooks/redmine-unicorn-nginx/templates/default/nginx.conf.erb
user www-data;
worker_processes 4;
pid /var/run/nginx.pid;

events {
	worker_connections 768;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	keepalive_timeout 65;
	types_hash_max_size 2048;
	# server_tokens off;

	# server_names_hash_bucket_size 64;
	# server_name_in_redirect off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	##
	# Gzip Settings
	##

	gzip on;
	gzip_disable "msie6";

	# gzip_vary on;
	# gzip_proxied any;
	# gzip_comp_level 6;
	# gzip_buffers 16 8k;
	# gzip_http_version 1.1;
	# gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

	##
	# nginx-naxsi config
	##
	# Uncomment it if you installed nginx-naxsi
	##

	#include /etc/nginx/naxsi_core.rules;

	##
	# nginx-passenger config
	##
	# Uncomment it if you installed nginx-passenger
	##
	
	#passenger_root /usr;
	#passenger_ruby /usr/bin/ruby;

    upstream redmine {
        server 127.0.0.1:5001;
    }

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}


#mail {
#	# See sample authentication script at:
#	# http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# 
#	# auth_http localhost/auth.php;
#	# pop3_capabilities "TOP" "USER";
#	# imap_capabilities "IMAP4rev1" "UIDPLUS";
# 
#	server {
#		listen     localhost:110;
#		protocol   pop3;
#		proxy      on;
#	}
# 
#	server {
#		listen     localhost:143;
#		protocol   imap;
#		proxy      on;
#	}
#}
```

```ruby:chef-repo/cookbooks/redmine-unicorn-nginx/templates/default/unicorn.rb.erb
worker_processes 2

listen File.expand_path("tmp/sockets/unicorn.sock", ENV['RAILS_ROOT'])
pid File.expand_path("tmp/pids/unicorn.pid", ENV['RAILS_ROOT'])

timeout 60

preload_app true

stdout_path File.expand_path("log/unicorn.stdout.log", ENV['RAILS_ROOT'])
stderr_path File.expand_path("log/unicorn.stderr.log", ENV['RAILS_ROOT'])

GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true

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

  old_pid = "#{server.config[:pid]}.oldbin"
    if old_pid != server.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
      end 
    end 

    sleep 1
  end 

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end
```

```ruby:chef-repo/cookbooks/redmine-unicorn-nginx/templates/default/virtual.vconf.erb
server {
  listen 80;
  server_name <%= node[:nginx][:server_name] %>;

  access_log /var/log/nginx/redmine_access.log;
  error_log /var/log/nginx/redmine_error.log;

  proxy_connect_timeout 60;
  proxy_read_timeout    60;
  proxy_send_timeout    60;

  # auth_basic "Secret Area";
  # auth_basic_user_file "/home/www/rails/redmine/.htpasswd";

  location / {

    root /usr/local/redmine/public;
    if (-f $request_filename){
      break;
    }

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://redmine;
    proxy_redirect off;
  }
}
```