Run Rails 2 Application on Shared Hosting

By Jimmy Bonney | December 7, 2013
Rails

Redmine is a project management web application. It comes with plenty of features and this would deserve a post in itself to go through them all but this is another story. We have been using Redmine mainly to track issues and submit new ideas for our applications. Currently, it is in use for D-Sight Desktop and its different plugins and it allows us to make sure that each release get its fair share of bug fix (when needed) and new features.

The beauty of Redmine is that it can be easily self-hosted which really makes it a tool of choice (understand feature-full for a really low price) when getting the company off the ground. In our case, problems started a couple of months ago after our hosting provider decided to go through a server migration which resulted in our Redmine application not being able to run any more.

Following the server migration, plenty of issues started popping up and priorities being what they are, Redmine availability got postponed. Well, as everything else, at some point, this needed to get fixed and this post will detail what needed to be done to restore access.

Redmine

Run Application Through CPanel

Starting the application through CPanel simply failed. Any attempt would provide the traditional message that the application was started but going back to the list of applications would just displayed a status “Stopped” in front of the application.

Ruby Gems Update

It seems that the gems that were originally installed on the server were not available any more. An update was probably performed during the migration resulting in gems being simply replaced.

The shared hosting provider allow to install some gem for the local user. So a rake gem:install can take care of installing missing gems locally but this requires that the application also look into the current user gem folder.

Bash Profile

Ensure that the GEM_PATH is correctly defined and contained the current user local gems folder. Edit your ~/.bash_profile and ensure that the following is in place (in our case, there was some error in the GEM_PATH variable that needed to be fixed).

export RAILS_ENV="production"
export GEM_HOME="/home/username/.gem/ruby/1.8"
export GEM_PATH="/home/username/.gem/ruby/1.8/gems:/home/username/.gem/ruby/1.8:/home/username/ruby/gems/1.8:/usr/lib/ruby/gems/1.8"
export GEM_CACHE=$GEM_HOME/cache
export PATH=$PATH:$GEM_HOME/bin
[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"

Redmine Environment

For the remaining of the article, consider that the application is installed under ~/rails_apps/redmine/.

Have a look in your Rails application at the config/environment.rb file. Some of the variables that need to be looked into are the following:

ENV['RAILS_ENV'] ||= 'production' # If no RAILS_ENV is specified, run in production
# ENV['RAILS_RELATIVE_URL_ROOT'] = '/redmine' # We had to comment this (see below)
RAILS_GEM_VERSION = '2.3.18' unless defined? RAILS_GEM_VERSION

In our case, I had to comment the line ENV['RAILS_RELATIVE_URL_ROOT'] = '/redmine' so that it doesn’t consider that the application is in the redmine subfolder. It used to work properly before with this setting but I think that the changes made in the .htaccess file (see below) cause some problems when trying to access the static resources (images, CSS, JavaScript, etc…).

Ensure that the RAILS_GEM_VERSION specified is available among the gems that are installed. To do so, one can run the following command:

gem list --local | grep rails

The output will be something similar to the following:

coffee-rails (3.2.2)
jquery-rails (2.0.2)
odbc-rails (1.5)
rails (2.3.18, 2.3.15, 2.3.14, 2.3.11)
sass-rails (3.2.5)

Run the Application

From the application folder (~/rails_apps/redmine/), see if the application starts. This is simply done by executing mongrel_rails start -p 12001. If our case, it didn’t start at this stage and we were welcome by the following error message:

Error calling Dispatcher.dispatch #<NoMethodError: You have a nil object when you didn't expect it! You might have expected an instance of Array. The error occurred while evaluating nil.split>

“Fortunately”, this error has been around for a while and a fix is available. Create a file config/initializers/mongrel.rb and paste the content found in this gist. The content might need to be adapted to reflect the Rails version that you are using. Below is the modification to include multiple version of Rails.

if ['2.3.8', '2.3.9', '2.3.10', '2.3.11', '2.3.14', '2.3.15', '2.3.16', '2.3.18'].include?(Rails.version) && Gem.available?('mongrel', Gem::Requirement.new('~>1.1.5')) && self.class.const_defined?(:Mongrel)

  # Pulled right from latest rack. Old looked like this in 1.1.0 version.
  #
  # def [](k)
  #   super(@names[k] ||= @names[k.downcase])
  # end
  #
  module Rack
    module Utils
      class HeaderHash < Hash
        def [](k)
          super(@names[k]) if @names[k]
          super(@names[k.downcase])
        end
      end
    end
  end

  # Code pulled from the ticket above.
  #
  class Mongrel::CGIWrapper
    def header_with_rails_fix(options = 'text/html')
      @head['cookie'] = options.delete('cookie').flatten.map { |v| v.sub(/^\n/,'') } if options.class != String and options['cookie']
      header_without_rails_fix(options)
    end
    alias_method_chain :header, :rails_fix
  end

  # Pulled right from 2.3.8 ActionPack. Simple diff was
  #
  # if headers.include?('Set-Cookie')
  #   headers['cookie'] = headers.delete('Set-Cookie').split("\n")
  # end
  #
  # to
  #
  # if headers['Set-Cookie']
  #   headers['cookie'] = headers.delete('Set-Cookie').split("\n")
  # end
  #
  module ActionController
    class CGIHandler
      def self.dispatch_cgi(app, cgi, out = $stdout)
        env = cgi.__send__(:env_table)
        env.delete "HTTP_CONTENT_LENGTH"
        cgi.stdinput.extend ProperStream
        env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
        env.update({
          "rack.version" => [0,1],
          "rack.input" => cgi.stdinput,
          "rack.errors" => $stderr,
          "rack.multithread" => false,
          "rack.multiprocess" => true,
          "rack.run_once" => false,
          "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
        })
        env["QUERY_STRING"] ||= ""
        env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
        env["REQUEST_PATH"] ||= "/"
        env.delete "PATH_INFO" if env["PATH_INFO"] == ""
        status, headers, body = app.call(env)
        begin
          out.binmode if out.respond_to?(:binmode)
          out.sync = false if out.respond_to?(:sync=)
          headers['Status'] = status.to_s
          if headers['Set-Cookie']
            headers['cookie'] = headers.delete('Set-Cookie').split("\n")
          end
          out.write(cgi.header(headers))
          body.each { |part|
            out.write part
            out.flush if out.respond_to?(:flush)
          }
        ensure
          body.close if body.respond_to?(:close)
        end
      end
    end
  end

end

Once this is in place, the application should start properly. It can then be daemonized in order to run it in the background.

mongrel_rails start -p 12001 -d

Access the application

If the application runs, we are halfway there. It needs of course to be accessible from the outside so check that you can access http://example.com:12001 from your browser. In our case, no luck here.

From the server itself, check that the application is accessible. This is done by running a curl 127.0.0.1:12001 from the console. This should probably work at this stage and if you are restoring access to Redmine, then you should see something like:

<html><body>You are being <a href="http://127.0.0.1:12001/login?back_url=http%3A%2F%2F127.0.0.1%3A12005%2F">redirected</a>.</body></html>

HTAccess

Then, access from the outside is a matter of setting properly Apache rewrite rules. In our case, we had to identify the HTAccess rules that were previously in place for the application and update them.

All rules related to the Redmine application that were in ~/www/.htaccess have been commented out. We have created a .htaccess file inside the application folder itself (i.e. in ~/rails_apps/redmine/.htaccess) and filled it with the following:

RewriteEngine on
RewriteCond %{HTTP_HOST} ^redmine.example.com$ [OR]
RewriteCond %{HTTP_HOST} ^www.redmine.example.com$
RewriteRule ^(.*)$ "http://127.0.0.1:12001%{REQUEST_URI}" [P,L]

As a result, Redmine is now accessible directly from http://redmine.example.com. Since this is on a subdomain of the application, this is the reason why # ENV['RAILS_RELATIVE_URL_ROOT'] = '/redmine' needed to be commented in config/environment.rb.

Some additional resources

I haven’t completely tried out the following solutions but they may offer a leaner and clearer approach to what we have done here:



comments powered by Disqus