Wednesday, March 23, 2011

Install Rails 3 application to cPanel in sub-URI, howto

Recently, I successfully installed a Rails 3.0.3 application (using Ruby 1.8.7) on a plain-vanilla webhost with cPanel (which normally is restricted to Rails 2).

It can be managed in the normal way (started and stopped) on cPanel's webpage, 'Manage Ruby on Rails Applications', because of additional code I wrote in the Rails app.

Here's how I did it:

o See the webhost's current Ruby version and gem environment (for your_user_name) with:

cron> gem env

(If shell access is unavailable, command lines can be run as one-time cron jobs by specifying (year) month, day, hour and minute.)

o Ask the webhost to update if they don't have the latest RubyGems system software:

root> gem update --system

o Create a Ruby on Rails application (on the cPanel Ruby on Rails page) specifying application path, 'rails_apps/your_app_name'.

o In cPanel's File Manager (with the option to show hidden files) navigate to $HOME/rails_apps/your_app_name and delete the contents just made. Upload your Rails 3 application (there, compressing with 'zip' gives the relative pathnames required by cPanel) and extract it.

o Correct your cPanel user's gem environment by uploading to $HOME/.gemrc the following:

---
gem: --no-rdoc --no-ri

# Note: a non-default value adds another gempath.
# Is default for rubygems 1.5.2:
gemhome: /home/your_user_name/.gem/ruby/1.8

gempath:
# Is default for rubygems 1.5.2:
- /home/your_user_name/.gem/ruby/1.8
# Your app's vendor bundle:
- /home/your_user_name/rails_apps/your_app_name/vendor/bundle/ruby/1.8
# Change this to the webhost's gem repository location:
- /usr/lib/ruby/gems/1.8

o The webhost's cPanel Software/Services RubyGems page might be incorrect:

oo It might say the location of your ruby gems is '/home/your_user_name/ruby/gems'. This is wrong; Rubygems software currently uses, '/home/your_user_name/.gem/ruby/1.8'.

oo It might recommend adding a gem repository to the include path. This is unnecessary; don't add either of the following:

$:.push("/home/your_user_name/ruby/gems")
$:.push("/home/your_user_name/.gem/ruby/1.8")

o Install the bundler gem:

cron> gem install bundler

o Generate Gemfile.lock with:

cron> cd $HOME/rails_apps/your_app_name; bundle install --path vendor/bundle

o Ask the webhost to install (in the system gem repository) any gems Bundler failed to install, for instance, when Bundler tries to compile native extensions, which fails for cPanel users. For me, this was:

root> gem install mysql2

o Replace the Bundler code in config/boot.rb with a fixed list of gems:

=begin
# Set up gems listed in the Gemfile.
gemfile = File.expand_path('../../Gemfile', __FILE__)
begin
ENV['BUNDLE_GEMFILE'] = gemfile
require 'bundler'
Bundler.setup
rescue Bundler::GemNotFound => e
STDERR.puts e.message
STDERR.puts "Try running `bundle install`."
exit!
end if File.exist?(gemfile)
=end

o To make the list, sort Gemfile.lock and keep only its highest version of each gem. My fixed list was:

require 'rubygems'

require 'mysql2'

# Moved arel up, because otherwise got error:
## /usr/lib/ruby/site_ruby/1.8/rubygems.rb:274:in `activate': can't activate arel (= 2.0.7, runtime) for [], already activated arel-2.0.8 for ["activerecord-3.0.3"] (Gem::LoadError)

gem 'arel', '=2.0.7'

gem 'abstract', '=1.0.0'
gem 'actionmailer', '=3.0.3'
gem 'actionpack', '=3.0.3'
gem 'activemodel', '=3.0.3'
gem 'activerecord', '=3.0.3'
gem 'activeresource', '=3.0.3'
gem 'activesupport', '=3.0.3'
gem 'builder', '=2.1.2'
gem 'erubis', '=2.6.6'
gem 'i18n', '=0.5.0'
gem 'mail', '=2.2.15'
gem 'mime-types', '=1.16'
gem 'polyglot', '=0.3.1'
gem 'rack', '=1.2.1'
gem 'rack-mount', '=0.6.13'
gem 'rack-test', '=0.5.7'
gem 'rails', '=3.0.3'
gem 'railties', '=3.0.3'
gem 'rake', '=0.8.7'
gem 'thor', '=0.14.6'
gem 'treetop', '=1.4.9'
gem 'tzinfo', '=0.3.24'

o Seamonkey's caching causes problems in testing. The setting, 'Menu-Edit-Preferences-Advanced-Cache-Compare the page in the cache to the page on the network', should be 'Every time I view the page'. This still did not prevent all old, cached pages even with page reloads. I recommend using another browser, such as Opera to test your application.

o For my Rails 3 application, I used port 12009, one higher than cPanel's Mongel port for Rails (12008).

o Besides the cPanel button, you can start the application yourself in the cPanel way by:

cron> cd $HOME/rails_apps/your_app_name; /usr/bin/ruby /usr/bin/mongrel_rails start -p cPanel_Mongel_port; -d -e development -P log/mongrel.pid

o When troubleshooting, you can also start the application yourself by:

cron> cd $HOME/rails_apps/your_app_name; $HOME/.gem/ruby/1.8/bin/rails server webrick --port=Rails_3_port

o In config/environment.rb, add this line at the top:

unless (STARTED_BY_CPANEL='script/rails' != $PROGRAM_NAME)

o and these lines at the bottom:

else
load File.expand_path '../../monkey_patch_mongrel_1.1.5/start_webrick.rb', __FILE__
end

o Make a directory at the app root called, 'monkey_patch_mongrel_1.1.5'. Into it, place a file called 'dispatcher.rb' containing:

## Work around Mongrel source file: mongrel-1.1.5/lib/mongrel/rails.rb: 148
## require 'dispatcher'

module ActionController
class AbstractRequest
def relative_url_root=
end
end
end

o Also place a file there called 'start_webrick.rb' containing:

p Time.now, 'in '+__FILE__

# Versions tested on:
# Apache: 2.2.15
# Architecture: x86_64
# cPanel: 11.28.86
# cPanel Pro: 1.0 (RC1)
# Hosting package: Starter
# Kernel: 2.6.9-89.31.1.ELsmp (Linux)
# Mongrel: 1.1.5 (cPanel's)
# MySQL: 5.1.45
# Rails: 3.0.3
# Ruby: 1.8.7 patchlevel 330

module MyStartup
require 'pathname'

MY_RAILS_ENV=ENV['RAILS_ENV'] # Set by cPanel's Mongrel.

PROGRAM_FILE=Pathname(__FILE__).realpath
pfd=[]; PROGRAM_FILE.descend{|e| pfd << e}
USER_HOME=pfd.at 1
APP_ROOT= pfd.at -3
SERVER='webrick'
PORT='12009'

ARGUMENTS="--environment=#{MY_RAILS_ENV} --port=#{PORT}"
# For debugging, change to '':
REDIRECT_OUTPUT='> /dev/null'

class GemPathEntry
SYSTEM = Pathname('/').join *%w[ usr lib ruby gems 1.8 ]
USER = USER_HOME .join *%w[ .gem ruby 1.8 ]
APP_BUNDLE = APP_ROOT .join *%w[ vendor bundle ruby 1.8 ]
end
MY_GEM_HOME= GemPathEntry::USER
MY_GEM_PATH=[GemPathEntry::APP_BUNDLE, GemPathEntry::USER, GemPathEntry::SYSTEM].join ':'

# We might need Bundler in system gems. Or, as working now, in user (.gem) gems; I don't remember.

# The following line doesn't work because Bundler (1.0.10) "bundle install --binstubs" rewrites gem executables to invoke Bundler:
## r = APP_ROOT.
r = GemPathEntry::APP_BUNDLE
RAILS_COMMAND=['exec',r.join(*%w[bin rails]),'server',SERVER,ARGUMENTS,REDIRECT_OUTPUT].join ' '

REQUIRED_ENVIRONMENT_VARIABLES=[
"HOME=#{ USER_HOME }",
"RAILS_ENV=#{ MY_RAILS_ENV }",
"GEM_HOME=#{ MY_GEM_HOME }",
"GEM_PATH=#{ MY_GEM_PATH }",
].join ' '

def self.stop_process(name,pid,signal)
begin
p "I Stopping #{name} pid #{pid} at #{Time.now}"
Process.kill signal, pid
p "I #{Process.waitall.inspect}, #{name} finished at #{Time.now}"
rescue Errno::EINVAL, Errno::ESRCH
p "I No #{name} process #{pid}"
end
# Got error, undefined local variable or method `pwa' with:
## p pwa, "All child processes finished at #{Time.now}." unless (pwa=Process.waitall).empty?
# Sometimes there are other processes, I don't know why; so wait for them.
pwa=Process.waitall
p "I #{pwa.inspect}, all child processes finished at #{Time.now}" unless pwa.empty?
end

# Rack (1.2.1) fails with Rails 3.0.3 and any Mongrel, although Webrick works, per:
# https://github.com/rack/rack/issues/35

MONGREL_PID_FILE=APP_ROOT.join *%w[ log mongrel.pid ]

p Time.now
p "I Program name ($0) is #{$PROGRAM_NAME}"
p "I In #{PROGRAM_FILE}"
$LOAD_PATH.unshift APP_ROOT.join 'monkey_patch_mongrel_1.1.5'

unless WEBRICK_MONITOR_PID=Process.fork # Mongrel doesn't stop all threads, so we use a process.
p "I Starting Webrick monitor pid #{WEBRICK_MONITOR_PID} at #{Time.now} using command:"
p (c="export #{REQUIRED_ENVIRONMENT_VARIABLES}; cd #{APP_ROOT}; #{RAILS_COMMAND}")
Process.exec c unless WEBRICK_PID=Process.fork # Fork and replace another process.
p "I Starting Webrick pid #{WEBRICK_PID} at #{Time.now}"
Signal.trap 'TERM' do
stop_process 'Webrick', WEBRICK_PID, 'INT'
end
p "I #{Process.waitall.inspect}, Webrick finished (itself); stopping Mongrel at #{Time.now}"
unless File.exist? MONGREL_PID_FILE.to_s
p "I File '#{MONGREL_PID_FILE}' not found"
else
`#{s='mongrel_rails stop'}`
p "I '#{s}' finished at #{Time.now}"
end
Process.exit
end

Kernel.at_exit do # Handle being stopped by Mongrel.
stop_process 'Webrick monitor', WEBRICK_MONITOR_PID, 'TERM'
end

end

o The following rewrite rules require you to make this symlink:
/home/your_user_name/public_html/your_app_name ->
/home/your_user_name/rails_apps/your_app_name/public

ln -s $HOME/rails_apps/your_app_name/public $HOME/public_html/your_app_name

o At least in Rails 3.0.3, the following config/application.rb statement, which allows your application to generate the proper sub-URI URL's for its static assets such as images, requires an extra '/your_app_name' in the RewriteCond directive for most cached pages:

config.action_controller.asset_path=proc{|p| "/your_app_name#{p}"}

o Note that cPanel users cannot change Apache's system-level configuration file (/usr/local/apache/conf/httpd.conf), for instance to add Alias directives, so the following applies:

'The most common situation in which mod_rewrite is the right tool is when the very best solution requires access to the server configuration files, and you don't have that access. Some configuration directives are only available in the server configuration file. So if you are in a hosting situation where you only have .htaccess files to work with, you may need to resort to mod_rewrite.' --from http://httpd.apache.org/docs/current/rewrite/avoid.html

o The Pattern argument to RewriteRule must be without leading slash (it is stripped by Apache) or trailing slash. Its Substitution argument must have the leading slash.

o Apache's directive, DirectorySlash redirects bare sub-URI requests to '/your_app_name/', since 'your_app_name' is a symlink to your application's directory, 'public'. Similarly, Apache normally redirects bare HTTP_HOST requests to '/' because 'public_html' is a directory.

o Per: httpd-docs-2.2.14.en/mod/mod_rewrite.html#rewriterule
Per-directory Rewrites: When using the rewrite engine in .htaccess files the per-directory prefix (which always is the same for a specific directory) is automatically removed for the pattern matching and automatically added after the substitution has been done.

o My file, 'public_html/.htaccess' has these directives regarding cache expiration:

# Requires mod_expires to be enabled.
<IfModule mod_expires.c>
# Enable expirations.
ExpiresActive On
# Cache all files for 2 weeks after access (A).
ExpiresDefault A1209600
# Do not cache dynamically generated pages.
ExpiresByType text/html A1
</IfModule>

o Apache's <if> directive is unavailable until version 2.3, per:
http://serverfault.com/questions/238832/how-should-i-use-the-if-directive-in-htaccess

o Apache's FallbackResource directive is unavailable until version 2.2.16, per:
http://httpd.apache.org/docs/2.2/mod/mod_dir.html#fallbackresource

o Place this .htaccess file in your application's public directory:

#
# Rails 3.0.3 cPanel Apache settings.
#
# Don't show directory listings for URLs which map to a directory.
Options -Indexes
# Set the default handler to none.
DirectoryIndex
# Don't follow symbolic links in this directory or below.
Options -FollowSymLinks

# Rewrite module.
<IfModule mod_rewrite.c>
RewriteEngine on
# Per http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html#rewriteoptions :
RewriteOptions inherit
RewriteBase /your_app_name

# 1. Simplify the rules for your sub-URI by redirecting a missing subdomain to 'www.':

RewriteCond %{HTTP_HOST} ^your_user_name\.com$ [NC]
RewriteRule ^(.*)$ http://www.your_user_name.com/your_app_name/$1 [L,R=301]

# 2. Rewrite your Rails application URI's to various files if they exist.

# 2.1. Rewrite its page-cached URI's:

# 2.1.1. Try to rewrite the sub-URI root URI to a file ('public/your_app_name.html') with path '/home/your_user_name/public_html/your_app_name/your_app_name.html':

RewriteCond %{HTTP_HOST} ^www\.your_user_name\.com$ [NC]
RewriteCond %{REQUEST_FILENAME}your_app_name.html -f
#SHOW RewriteRule ^$ /your_app_name/your_app_name.html?dr=%{DOCUMENT_ROOT}&rf=%{REQUEST_FILENAME} [L,R=301]
RewriteRule ^$ /your_app_name/your_app_name.html [L]

# 2.1.2. Try to rewrite a sub-URI, non-root URI to a file with extension '.html' (under tree, 'public/your_app_name/') by prefixing '/home/your_user_name/public_html/your_app_name' and suffixing '.html':

RewriteCond %{HTTP_HOST} ^www\.your_user_name\.com$ [NC]
RewriteCond %{DOCUMENT_ROOT}/your_app_name%{REQUEST_URI}.html -f
#SHOW RewriteRule ^(.+)$ /your_app_name/$1.html?dr=%{DOCUMENT_ROOT}&ru=%{REQUEST_URI}&rf=%{REQUEST_FILENAME} [L,R=301]
RewriteRule ^(.+)$ /your_app_name/your_app_name/$1.html [L]

# 3. Rewrite all other requests (except existing files) to the Rails port:

RewriteCond %{HTTP_HOST} ^www\.your_user_name\.com$ [NC]
RewriteCond %{REQUEST_FILENAME} !-f
#SHOW RewriteRule ^(.*)$ /your_app_name/your_app_name.html?rf=%{REQUEST_FILENAME} [L,R=301]
RewriteRule ^(.*)$ http://127.0.0.1:12009/your_app_name/$1 [L,P]

</IfModule>

o References:
http://httpd.apache.org/docs/2.2/
http://httpd.apache.org/docs/2.2/mod/quickreference.html
http://httpd.apache.org/docs/2.2/rewrite/
http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html
http://httpd.apache.org/docs/1.3/mod/mod_rewrite.html (simpler)
http://httpd.apache.org/docs/2.2/rewrite/vhosts.html
http://wiki.apache.org/httpd/WhenNotToUseRewrite
http://borkweb.com/story/apache-rewrite-cheatsheet
http://www.askapache.com/htaccess/crazy-advanced-mod_rewrite-tutorial.html

o Then as a result, if you start your application, its cPanel Mongrel log in log/mongrel.log should look like this:

** Daemonized, any open files are closed. Look at log/mongrel.pid and log/mongrel.log for info.
** Starting Mongrel listening at 0.0.0.0:12008
** Starting Rails with development environment...
Wed Mar 23 12:29:15 -0700 2011
"in /home/your_user_name/rails_apps/your_app_name/config/environment.rb"
Wed Mar 23 12:29:15 -0700 2011
"in /home/your_user_name/rails_apps/your_app_name/monkey_patch_mongrel_1.1.5/start_webrick.rb"
Wed Mar 23 12:29:15 -0700 2011
"I Program name ($0) is /usr/bin/mongrel_rails"
"I In /home/your_user_name/rails_apps/your_app_name/monkey_patch_mongrel_1.1.5/start_webrick.rb"
"I Starting Webrick monitor pid at Wed Mar 23 12:29:15 -0700 2011 using command:"
"export HOME=/home RAILS_ENV=development GEM_HOME=/home/.gem/ruby/1.8 GEM_PATH=/home/your_user_name/rails_apps/your_app_name/vendor/bundle/ruby/1.8:/home/.gem/ruby/1.8:/usr/lib/ruby/gems/1.8; cd /home/your_user_name/rails_apps/your_app_name; exec /home/your_user_name/rails_apps/your_app_name/vendor/bundle/ruby/1.8/bin/rails server webrick --environment=development --port=12009 > /dev/null"
"I Starting Webrick pid 15175 at Wed Mar 23 12:29:15 -0700 2011"
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart).
** Rails signals registered. HUP => reload (without restart). It might not work well.
** Mongrel 1.1.5 available at 0.0.0.0:12008
** Writing PID file to log/mongrel.pid
[2011-03-23 12:29:16] INFO WEBrick 1.3.1
[2011-03-23 12:29:16] INFO ruby 1.8.7 (2009-06-12) [x86_64-linux]
[2011-03-23 12:29:16] INFO WEBrick::HTTPServer#start: pid=15175 port=12009

o If you use the cPanel button to stop your application, it should look like this:

** TERM signal received.
"I Stopping Webrick monitor pid 22436 at Wed Mar 23 12:47:43 -0700 2011"
"I Stopping Webrick pid 22437 at Wed Mar 23 12:47:43 -0700 2011"
[2011-03-23 12:47:43] INFO going to shutdown ...
[2011-03-23 12:47:43] INFO WEBrick::HTTPServer#start done.
"I [[22437, #<Process::Status: pid=22437,exited(0)>]], Webrick finished at Wed Mar 23 12:47:43 -0700 2011"
"I [], Webrick finished (itself); stopping Mongrel at Wed Mar 23 12:47:43 -0700 2011"
"I File '/home/your_user_name/rails_apps/your_app_name/log/mongrel.pid' not found"

o If, from inside your application, you also want to stop cPanel's Mongrel server and Webrick (I did, because my application makes static pages), add this to one of your controllers:

WEBRICK_PID_PATH=App.root.join *%w[ tmp pids server.pid ]
WEBRICK_PID = begin
f=File.new WEBRICK_PID_PATH.to_s, 'r'
s=f.gets("\n").chomp "\n"
f.close
s.to_i if s.present?
rescue Errno::ENOENT
nil
end
application_PID=Process.pid

def stop_application(s)
logger.info "I #{s}; sending INT to application PID #{application_PID}"
Process.kill 'INT', application_PID
end

def stop_server
# Attempt to stop Webrick server gracefully by sending it SIGINT:
webrick_killed=false
if WEBRICK_PID.present? && WEBRICK_PID > 0
begin
logger.info "I sending INT to Webrick PID #{WEBRICK_PID}; application PID is #{application_PID}"
Process.kill 'INT', WEBRICK_PID
webrick_killed=true
rescue Errno::EINVAL, Errno::ESRCH
end
end
# Handle various unusual conditions:
s=case
when WEBRICK_PID.blank?
'No Webrick server.pid file found'
when (WEBRICK_PID <= 0)
"Bad value in Webrick server.pid file: #{WEBRICK_PID}"
when (WEBRICK_PID != application_PID)
"Server PID #{WEBRICK_PID} differs from application's: not Webrick?"
when (!webrick_killed)
"No process #{WEBRICK_PID}"
# TODO: Handle hung Webrick server?
end
stop_application s if s
end

o If you stop Mongrel from within your application, it should look like this:

[2011-03-23 12:30:46] INFO going to shutdown ...
[2011-03-23 12:30:47] INFO WEBrick::HTTPServer#start done.
"I [[15175, #<Process::Status: pid=15175,exited(0)>]], Webrick finished (itself); stopping Mongrel at Wed Mar 23 12:30:47 -0700 2011"
** TERM signal received.
"I Stopping Webrick monitor pid 15174 at Wed Mar 23 12:30:47 -0700 2011"
"I Stopping Webrick pid 15175 at Wed Mar 23 12:30:47 -0700 2011"
"I No Webrick process 15175"
"I [[16266, #<Process::Status: pid=16266,exited(0)>]], all child processes finished at Wed Mar 23 12:30:47 -0700 2011"
"I 'mongrel_rails stop' finished at Wed Mar 23 12:30:47 -0700 2011"
"I [[15174, #<Process::Status: pid=15174,exited(0)>]], Webrick monitor finished at Wed Mar 23 12:30:47 -0700 2011"

References:
Webmaster gallery - cPanel Rails 3 app - Mark D. Blackwell

Copyright (c) 2011 Mark D. Blackwell.

2 comments:

  1. awesome!! thanks so much for posting this.

    ReplyDelete
  2. All of us are on the lookout for information on something like this. I Myself have gone through several blogs to build up on knowledge about this.We look forward to the next posts !!Website Hosting

    ReplyDelete

Thanks for commenting on my post!