前にこういう記事 書いたんですけど、Redmine プロジェクトは結構活発でついこの間 1.4.0 がリリースされてました。なんと Ruby1.9 系へ対応!Gemfile も提供されるようになってよりインストールが簡単になってたのでかっとなってやってみました。ついでにこちらもだいぶ枯れてきた rbenv+ruby-build を使い、HTTP サーバとしては unicorn を使ってみました。
rbenv+ruby-build のインストール
git は入っている前提ですが、超簡単です。
$ cd
$ git clone https://github.com/sstephenson/rbenv.git .rbenv
$ mkdir -p ~/.rbenv/plugins
$ cd ~/.rbenv/plugins
$ git clone https://github.com/sstephenson/ruby-build.git
あとは.bashrc とかに以下の記述をして再読込。
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"
これで rbenv コマンドが(タブ補完つきで)使うことができます。さて、早速 ruby を入れたいんですが、後ほどハマらないように先に OS のパッケージ管理で openssl の開発パッケージを入れておきます。
# yum install openssl-devel
Redmine1.4 からはついに ruby1.9 が使えるので 1.9.3 を入れてみましょう。
$ rbenv install 1.9.3-p125
$ rbenv rehash
$ rbenv versions
1.9.3-p125
Redmine のダウンロード
tar.gz をダウンロードしてもいいんですが、有志の方が本家の svn を github に上げてくれいているのでそれを使ってみます。
$ cd
$ git clone https://github.com/redmine/redmine.git
$ cd redmine
$ git branch -a
master
remotes/origin/0.6-stable
remotes/origin/0.7-stable
remotes/origin/0.8-stable
remotes/origin/0.9-stable
remotes/origin/1.0-stable
remotes/origin/1.1-stable
remotes/origin/1.2-stable
remotes/origin/1.3-stable
remotes/origin/1.4-stable
remotes/origin/HEAD -> origin/master
remotes/origin/master
こういう感じでバージョンごとに stable の branch があるのでとりあえずこれに切り替えておけば多分 git pull するだけで stable の最新版を追いかけられる様な気がしました(勘で言ってます)。
$ cd ~/redmine
$ git checkout -b 1.4-stable remotes/origin/1.4-stable
$ git branch
* 1.4-stable
master
ではここで先程ビルドしたバージョンの ruby を使う様に rbenv の設定をします。
$ cd ~/redmine
$ rbenv local 1.9.3-p125
$ ruby -v
ruby 1.9.3p125 (2012-02-16 revision 34643) [x86_64-linux]
$ rbenv which ruby
/home/riywo/.rbenv/versions/1.9.3-p125/bin/ruby
このディレクトリ配下にいるときだけこの設定は効果を発揮しますので、別のディレクトリに移ると元に戻ります。おもしろいですね。
$ cd ~
$ rbenv which ruby
/usr/bin/ruby
bundler のインストール
基本的に gem パッケージはアプリケーション毎にローカルに持つんですけど、それを実現するための bundler パッケージだけは先程ビルドした ruby 本体の中に入れてしまいます。rbenv shell
とかでもいんですけど、先程rbenv local
で設定したディレクトリで作業しちゃいます。
$ cd ~/redmine
$ rbenv which gem
/home/riywo/.rbenv/versions/1.9.3-p125/bin/gem
$ gem install bundler
$ rbenv rehash
$ bundle -v
Bundler version 1.1.3
これ以降、このバージョンの ruby に対しては直接gem
コマンドを叩くことはありません。全て Gemfile という DSL に記述してbundle
コマンドで、アプリケーションローカルにインストールしていきます。
Redmine の依存モジュールをインストール
Redmine1.4 からは Gemfile が提供されているので何も考えずに叩くだけです。ただし、普通に叩くと ruby 本体の方に入ってしまうので、オプションでインストール場所をローカルにしておきます。Imagemagick とかポスグレとかは無視。MySQL のクライアントライブラリは先に頑張って入れておいて下さい。
$ cd ~/redmine
$ bundle install --path vendor/bundle --without development test rmagick postgresql sqlite
簡単ですね!ついでに Unicorn も入れておきたいので Gemfile.local というファイルを作ってもう一回実行します。すでにインストール先は設定されてるのでオプション不要です。
$ cd ~/redmine
$ echo 'gem "unicorn"' > Gemfile.local
$ bundle install
$ bundle --verbose list
初めてのインストールの場合
Redmine を初めてインストールする場合はドキュメントに従ってゴニョゴニョします。一応コマンド書いてますが、最新のドキュメントを常に参照して下さい。
MySQL のユーザは適当に。
create database redmine character set utf8;
create user 'redmine'@'localhost' identified by 'my_password';
grant all privileges on redmine.* to 'redmine'@'localhost';
config/database.yml は以下の様に。mysql2
を指定するのがポイントだそうです。
production:
adapter: mysql2
database: redmine
host: localhost
username: redmine
password: my_password
あとはひたすらrake
コマンド。ただし、bundler で gem を入れているのでbundle exec
で実行します。終わったら試しに WEBrick で起動してブラウザで見てみましょう。
$ cd ~/redmine
$ bundle exec rake -- generate_session_store
$ RAILS_ENV=production bundle exec rake db:migrate
$ RAILS_ENV=production bundle exec rake redmine:load_default_data
$ bundle exec script/server webrick -e production
1.2 とかからアップデートする場合
解説めんどいのでドキュメント見てください。。。大して難しくないです。rake
するときにbundle exec
するのをお忘れなく。
Unicorn で起動
あとはこれを unicorn で起動するだけです。起動方法のデファクトがよく分からなかったので、とりあえず慣れてる daemontools ならこんな感じかと。unicorn の config 自体はイマイチよく分かってないのでサンプルを適当にいじっただけ。
GitHub Javascript Strategy
Unless otherwise necessary (such as mobile development), the GitHub javascript codebase is based off jQuery. You can safely assume it will be included on every page.
All jquery plugins should be prefixed with jquery, such as jquery.facebox
All github-specific jquery plugins should be prefixed with jquery.github. Like jquery.github.repo_list.js
All page-specific files (that only run on ONE page) should be prefixed with page. page.billing.js
All javascript for the site should be put into a directory. Feel free to create new directories when it makes sense (gist, iphone, etc). There are a few directories that have special meanings:
common
- Contains all files that are used and shared between the main site and sub-sites (like gist)
rogue
- Files that are directly called by individual pages. Use for large libraries that are only used on one (or a very few) pages.
dev
- Files that are hosted elsewhere (such as google) that you will need if you do not have an internet connection.
Inside of each directory, you should note the difference between plugins and files where possible and split them into different directories (see github
).
On deploy, each top level directory is bundled into a compressed file. Any files that are inside the directory (and it's subfolders) will be bundled into one file and available at bundle_[directory].js
If you would like to include a bundle in your Rails templates, you reference bundles or individual files using the javascript_bundle
and javascript_dev
helpers.
javascript_dev ['jquery', 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js']
javascript_bundle 'common', 'github', 'rogue/s3_upload'
In production, this will include:
http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js
in production.
bundle_common.js
bundle_github.js
rogue/s3_upload.js
In development, this will include:
dev/jquery.js
All javascripts inside of the common
directory
All javascripts inside the github
directory
rogue/s3_upload.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
RAILS_ROOT ||= ENV["RAILS_ROOT"]
namespace :bundle do
task :all => [ :js, :css ]
task :js do
compression_method = "closure"
require 'lib/js_minimizer' if compression_method != "closure"
closure_path = RAILS_ROOT + '/lib/closure_compressor.jar'
paths = get_top_level_directories('/public/javascripts')
targets = []
paths.each do |bundle_directory|
bundle_name = bundle_directory.gsub(RAILS_ROOT + '/public/javascripts/', "")
files = recursive_file_list(bundle_directory, ".js")
next if files.empty? || bundle_name == 'dev'
target = RAILS_ROOT + "/public/javascripts/bundle_#{bundle_name}.js"
if compression_method == "closure"
`java -jar #{closure_path} --js #{files.join(" --js ")} --js_output_file #{target} 2> /dev/null`
else
File.open(target, 'w+') do |f|
f.puts JSMinimizer.minimize_files(*files)
end
end
targets << target
end
targets.each do |target|
puts "=> bundled js at #{target}"
end
end
task :css do
yuipath = RAILS_ROOT + '/lib/yuicompressor-2.4.1.jar'
paths = get_top_level_directories('/public/stylesheets')
targets = []
paths.each do |bundle_directory|
bundle_name = bundle_directory.gsub(RAILS_ROOT + '/public/stylesheets/', "")
files = recursive_file_list(bundle_directory, ".css")
next if files.empty? || bundle_name == 'dev'
bundle = ''
files.each do |file_path|
bundle << File.read(file_path) << "\n"
end
target = RAILS_ROOT + "/public/stylesheets/bundle_#{bundle_name}.css"
rawpath = "/tmp/bundle_raw.css"
File.open(rawpath, 'w') { |f| f.write(bundle) }
`java -jar #{yuipath} --line-break 0 #{rawpath} -o #{target}`
targets << target
end
targets.each do |target|
puts "=> bundled css at #{target}"
end
end
require 'find'
def recursive_file_list(basedir, ext)
files = []
Find.find(basedir) do |path|
if FileTest.directory?(path)
if File.basename(path)[0] == ?. # Skip dot directories
Find.prune
else
next
end
end
files << path if File.extname(path) == ext
end
files.sort
end
def get_top_level_directories(base_path)
Dir.entries(RAILS_ROOT + base_path).collect do |path|
path = RAILS_ROOT + "#{base_path}/#{path}"
File.basename(path)[0] == ?. || !File.directory?(path) ? nil : path # not dot directories or files
end - [nil]
end
end
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'find'
module BundleHelper
def bundle_files?
Rails.production? || Rails.staging? || params[:bundle] || cookies[:bundle] == "yes"
end
def javascript_bundle(*sources)
sources = sources.to_a
bundle_files? ? javascript_include_bundles(sources) : javascript_include_files(sources)
end
# This method assumes you have manually bundled js using a rake command
# or similar. So there better be bundle_* files.
def javascript_include_bundles(bundles)
output = ""
bundles.each do |bundle|
output << javascript_src_tag("bundle_#{bundle}", {}) + "\n"
end
output
end
def javascript_include_files(bundles)
output = ""
bundles.each do |bundle|
files = recursive_file_list("public/javascripts/#{bundle}", ".js")
files.each do |file|
file = file.gsub('public/javascripts/', '')
output << javascript_src_tag(file, {}) + "\n"
end
end
output
end
def javascript_dev(*sources)
output = ""
sources = sources.to_a
sources.each do |pair|
output << javascript_src_tag(Rails.development? ? "dev/#{pair[0]}" : pair[1], {})
end
output
end
def stylesheet_bundle(*sources)
sources = sources.to_a
bundle_files? ? stylesheet_include_bundles(sources) : stylesheet_include_files(sources)
end
# This method assumes you have manually bundled css using a rake command
# or similar. So there better be bundle_* files.
def stylesheet_include_bundles(bundles)
stylesheet_link_tag(bundles.collect{ |b| "bundle_#{b}"})
end
def stylesheet_include_files(bundles)
output = ""
bundles.each do |bundle|
files = recursive_file_list("public/stylesheets/#{bundle}", ".css")
files.each do |file|
file = file.gsub('public/stylesheets/', '')
output << stylesheet_link_tag(file)
end
end
output
end
def recursive_file_list(basedir, extname)
files = []
basedir = RAILS_ROOT + "/" + basedir
Find.find(basedir) do |path|
if FileTest.directory?(path)
if File.basename(path)[0] == ?.
Find.prune
else
next
end
end
files << path.gsub(RAILS_ROOT + '/', '') if File.extname(path) == extname
end
files.sort
end
end
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace :deploy do
desc "Shrink and bundle js and css"
task :bundle, :roles => :web, :except => { :no_release => true } do
run "cd #{current_path}; RAILS_ROOT=#{current_path} rake bundle:all"
end
end
after "deploy:update_code", "deploy:bundle"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
config.action_controller.asset_host = Proc.new do |source, request|
non_ssl_host = "http://assets#{source.hash % 4}.github.com"
ssl_host = "https://assets#{source.hash % 4}.github.com"
if request.ssl?
if source =~ /\.js$/
ssl_host
elsif request.headers["USER_AGENT"] =~ /(Safari)/
non_ssl_host
else
ssl_host
end
else
non_ssl_host
end
end
repo = Grit::Repo.new(RAILS_ROOT)
js = repo.log('master', 'public/javascripts', :max_count => 1).first
css = repo.log('master', 'public/stylesheets', :max_count => 1).first
ENV['RAILS_ASSET_ID'] = js.committed_date > css.committed_date ? js.id : css.id
これで 8080 ポートで待ち受けているので、後は Apache なり nginx なりからリバースプロキシしてあげれば完成かなと思います。
おわりに
だいぶすっきりしたなぁという印象です。ruby は bundler も rbenv もよくできていて、スタンドアロンな環境をさくさく作れていい感じだと思います。
また、Redmine プロジェクトはこれだけの結構大きいアプリケーションなのに精力的に開発されていてすばらしいと思いました。ちっぽけですが少しでも貢献できればと思い記事をしたためました。