The Ultimate Server - Ruby on Rails with Lighttpd, Apache2 with PHP, MySQL, PostgreSQL on Ubuntu Dapper Server 12

Posted by James Wilford Mon, 20 Nov 2006 18:22:00 GMT

As we all know, in the real world things are rarely simple, and the deployment of Ruby on Rails sites is certainly no exception to this rule. In fact it can sometimes seem that what Rails gives with its ease of development, it takes away when it comes to the quagmire of deployment. First you have to choose your server - Apache with FastCGI? Lighttpd? Apache with Mongrel cluster? The options can seem baffling to the newcomer.

Running first on a Debian platform, I initially deployed my Rails sites using Apache 1.3 with FastCGI. However, this proved so unreliable that eventually I was forced to take all my sites offline in order to protect the health of the rest of the system. Frequently I would notice sluggish performance, and log in to find a zombie ruby process hogging all available CPU. And I'm not alone.

So when I recently rebuilt my server using Ubuntu Dapper Server I decided to try LightTPD - AKA Lighty. The Mongrel cluster approach was out, as this requires Apache2.2, and I wanted to stick to the Ubuntu packages for ease of ongoing maintenance, and avoid compiling anything from source. My server also hosts a number of sites running happily on Apache and using PHP, so the new Lighty solution had to co-exist with these.

Based on my experience, I'm going to show how you too can build the ultimate server (tm) - one that can handle virtual hosting of Apache/PHP sites alongside multiple Rails sites on Lighty, where any site can use MySQL or PostgreSQL as its database, and all this can be made to coexist on one server using nothing more than an installation of RubyGems and the standard Ubuntu packages.

Assumptions and Requirements

Firstly I'm assuming your starting point is a bare install of Ubuntu Server. Don't select the LAMP option (although it might not hurt - I haven't tested it) as the following procedure will install everything you need. Secondly, I'm assuming you have more than one public IP available to your server. If you don't, you will need to run Lighty on a different port and proxy all your Rails sites through from Apache, as described in part 3 of this article, and also in more depth in this article.

Our finishing point is a multi-homed server with Lighty on one IP for Rails, Apache2 on another IP for Apache2/PHP sites, and MySQL and PostgreSQL support in Rails and PHP. I'm not covering things like Subversion and Capistrano here - that's a whole different can of worms. For a good tutorial on deploying onto Lighty using Capistrano, I highly recommend this article.

Part 1: Initial Setup and Package Installation

Pretty much everything that follows requires root priveleges, so to save your fingers some work, acquire a root shell:

james@ubuntu:~$ sudo -i
Password:
root@ubuntu:~#

To kick off, enable the Universe repositories in /etc/apt/sources.list - this is required for some packages such as irb. Then bring your server up to date and install build-essential, to allow building of mysql and postgres gems later:

apt-get update
apt-get upgrade
apt-get install build-essential

Next edit /etc/network/interfaces to configure your server with 2 IPs; this article will use 192.168.10.3 for Apache2 and 192.168.10.4 for Lighty. Obviously you should substitute your real, public IPs. Note the auto lines to ensure the interfaces come up on reboot:

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 inet static
        address 192.168.10.3
        netmask 255.255.255.0
        network 192.168.10.0
        gateway 192.168.10.1

auto eth0:1
iface eth0:1 inet static
        address 192.168.10.4
        netmask 255.255.255.0

Now install Ruby:

apt-get install ruby1.8 ruby1.8-dev irb1.8 rdoc1.8

create symlinks:

ln -s /usr/bin/ruby1.8 /usr/bin/ruby
ln -s /usr/bin/irb1.8 /usr/bin/irb

Install RubyGems:

wget http://rubyforge.org/frs/download.php/11289/rubygems-0.9.0.tgz
tar -xzf rubygems-0.9.0.tgz
cd rubygems-0.9.0
ruby setup.rb

Install Rails:

gem install rails --include-dependencies

Install PostgreSQL and postgres gem (ignore compilation errors):

apt-get install postgresql-8.1 postgresql-server-dev-8.1 libpgsql-ruby1.8
gem install postgres

Install MySQL and mysql gem (choose option 2):

apt-get install mysql-server libmysqlclient15-dev libmysql-ruby1.8
gem install mysql

Install fcgi gem (needed to use Rails with Lighty):

apt-get install libfcgi-dev libfcgi-ruby1.8
gem install fcgi

Make sure ruby is happy with everything installed:

root@ubuntu:~/rubygems-0.9.0# irb
irb(main):001:0> require 'fcgi'
=> true
irb(main):002:0> require 'mysql'
=> true
irb(main):003:0> require 'postgres'
=> true

Install web servers:

apt-get install apache2 libapache2-mod-php5 php5-mysqli php5-pgsql php5-gd
apt-get install lighttpd

Phew - that's it for package installation.

Part 2: Configuration

First set up your databases - I won't cover how to do that here. Next set up your Apache2 sites - by default Apache2 will greedily bind to all available interfaces, so you will need to edit the configuration to restrict it to only one IP, leaving the other free for Lighty. This is done by editing the /etc/apache2/ports.conf file to look something like this:

Listen 192.168.10.3:80

Then create your Apache2 virtual host configurations. In Ubuntu, vhosts live in /etc/apache2/sites-available and are activated using the command "a2ensite". In this example, I'm creating a site for example.com by creating the file /etc/apache2/sites-available/example.com as follows:

<VirtualHost 192.168.10.3:80>
  ServerName www.example.com
  ServerAlias example.com
  DocumentRoot /home/web/example.com/public_html
  Alias /webstat /home/web/example.com/stats
  CustomLog    /home/web/example.com/logs/access.log combined
  ErrorLog     /home/web/example.com/logs/error.log
</VirtualHost>

Note that the VirtualHost declaration uses the IP explicitly. In order for this to work, you must also edit the file /etc/apache2/sites-available/default which contains the NameVirtualHost directive, so that the first 2 lines read as follows:

NameVirtualHost 192.168.10.3:80
<VirtualHost 192.168.10.3:80>

Then activate your site and restart Apache2:

a2ensite example.com
/etc/init.d/apache2 restart

Netstat should now show Apache2 listening only on the desired IP:

root@ubuntu:/# netstat -ltnp | grep apache2
tcp        0      0 192.168.10.3:80         0.0.0.0:*               LISTEN     7187/apache2

Now its time to set up your Rails sites using Lighty. Here is a sample /etc/lighttpd/lighttpd.conf file for a couple of Rails sites, rails1.example.com and rails2.example.com. Note the server.bind line to restrict Lighty to its own IP and avoid clashing with Apache2:

server.modules = ("mod_rewrite", "mod_accesslog", "mod_fastcgi", "mod_status")
server.bind = "192.168.10.4"
server.port = 80
server.username = "www-data"
server.groupname = "www-data"
server.pid-file = "/var/run/lighttpd.pid"
accesslog.filename = "/var/log/lighttpd/access.log"
server.errorlog = "/var/log/lighttpd/error.log"
server.indexfiles = ( "index.html" )
server.document-root       = "/var/www/"

# assign mime types from /etc/mime.types
include_shell "/usr/share/lighttpd/create-mime.assign.pl"

#### status module
status.status-url = "/server-status"

# first rails site

var.rails1 = "/home/web/rails1.example.com"
$HTTP["host"] == "rails1.example.com" {
  server.document-root = var.rails1 + "/public"
  url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" )
  server.error-handler-404 = "/dispatch.fcgi"
  accesslog.filename = "/home/web/rails1.example.com/log/access.log"
  server.errorlog = "/home/web/rails1.example.com/log/error.log"
  server.indexfiles = ( "index.html" )
  fastcgi.server = ( ".fcgi" =>
    ( "localhost" =>
      ( "min-procs" => 1,
        "max-procs" => 2,
        "socket" => "/tmp/rails1.fcgi.socket",
        "bin-path" => var.rails1 + "/public/dispatch.fcgi",
        "bin-environment" => ( "RAILS_ENV" => "production" )
      )
    )
  )
}

# second rails site

var.rails2 = "/home/web/rails2.example.com"
$HTTP["host"] == "rails2.example.com" {
  server.document-root = var.rails2 + "/public"
  url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" )
  server.error-handler-404 = "/dispatch.fcgi"
  accesslog.filename = "/home/web/rails2.example.com/log/access.log"
  server.errorlog = "/home/web/rails2.example.com/log/error.log"
  server.indexfiles = ( "index.html" )
  fastcgi.server = ( ".fcgi" =>
    ( "localhost" =>
      ( "min-procs" => 1,
        "max-procs" => 2,
        "socket" => "/tmp/rails2.fcgi.socket",
        "bin-path" => var.rails2 + "/public/dispatch.fcgi",
        "bin-environment" => ( "RAILS_ENV" => "production" )
      )
    )
  )
}

Having created your config file, fire up Lighty (if you installed Apache2 first, it won't be running already, as port 80 was in use on all interfaces at that point):

/etc/init.d/lighttpd start

Part 3 - Advanced Stuff

So far, so good. By now you should have a server that can host pretty much anything you can throw at it, including multiple Rails sites as well as multiple Apache2/PHP sites, and these sites can use MySQL or PostgreSQL databases.

But what if you have a site that needs both? Supposing you have a PHP-based site running happily on Apache2, and you want to add a typo blog in a subdirectory, say coolsite.example.com/blog. You can't point the domain to both IPs, but what you can do is use Apache2's mod_proxy module to proxy the /blog subdirectory to Lighty. Your Apache2 vhost should look something like this:

<VirtualHost 192.168.10.3:80>
  ServerName coolsite.example.com
  DocumentRoot /home/web/coolsite.example.com/public_html
  Alias /webstat /home/web/coolsite.example.com/stats
  CustomLog    /home/web/coolsite.example.com/logs/access.log combined
  ErrorLog     /home/web/coolsite.example.com/logs/error.log

  ProxyPass /blog http://coolblog/blog
  ProxyPassReverse /blog http://coolblog/blog
  ProxyPreserveHost off
</VirtualHost>

You also need to enable mod_proxy:

a2enmod proxy
/etc/init.d/apache2 force-reload

This will cause Apache2 to proxy all requests for /blog to http://coolblog/blog. So you need to add an appropriate line to /etc/hosts:

192.168.10.4  coolblog

Then remove the symbolic link /etc/apache2/mods-enabled/proxy.conf, as by default this will deny access:

rm /etc/apache2/mods-enabled/proxy.conf

You can then set up a vhost in Lighty for the blog:

var.coolblog = "/home/web/blog"
$HTTP["host"] == "coolblog" {
  server.document-root = var.coolblog + "/public"
  url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" )
  server.error-handler-404 = "/dispatch.fcgi"
  accesslog.filename = "/dev/null"
  fastcgi.server = ( ".fcgi" =>
    ( "localhost" =>
      ( "min-procs" => 1,
        "max-procs" => 2,
        "socket" => "/tmp/coolblog.fcgi.socket",
        "bin-path" => var.coolblog + "/public/dispatch.fcgi",
        "bin-environment" => ( "RAILS_ENV" => "production" )
      )
    )
  )
}

And finally, add the following line to config/environment.rb in the typo installation:

ActionController::AbstractRequest.relative_url_root = "/blog"

And there you have it. You can now point the domain to the Apache2 IP, and it will proxy the /blog namespace through to Lighty. Note that the Lighty vhost is set to send its access log to /dev/null - this is because all the proxied requests will be logged by Apache2, and in any case, the Lighty logs will only show the requests as coming from Apache2, which doesn't make for very useful logs.

Wrap-up

Feel free to pick and choose bits from this tutorial - everyone's situation is different. But if what I've written here helps you, or you find a bug in my carefully-tested procedure, please leave a comment. In the meantime, happy configuring!

Syntax Highlighting for Scribbish 10

Posted by James Wilford Tue, 03 Oct 2006 21:52:00 GMT

So there I was, browsing through various Typo blogs and wondering why mine didn't have syntax highlighting of code blocks like all the cool kids out there. After some googling, and installing the syntax gem, I was puzzled to find that my code was still not being highlighted.

Turns out that, of course, the syntax highlighting relies on the CSS. And the stylesheet supplied with the Scribbish theme doesn't define any styles for the .typocode classes used by the syntax highlighting. So I decided to do something about that.

I started by having a look at the stylesheet for the Azure theme, which does do highlighting. Then I cracked open the /themes/scribbish/stylesheets/content.css file, and added the following:


/* Syntax highlighting */
#content .typocode_ruby .normal {}
#content .typocode_ruby .comment { color: #CCC; font-style: italic; border: none; margin: none; }
#content .typocode_ruby .keyword { color: #C60; font-weight: bold; }
#content .typocode_ruby .method { color: #9FF; }
#content .typocode_ruby .class { color: #074; }
#content .typocode_ruby .module { color: #050; }
#content .typocode_ruby .punct { color: #0D0; font-weight: bold; }
#content .typocode_ruby .symbol { color: #099; }
#content .typocode_ruby .string { color: #C03; }
#content .typocode_ruby .char { color: #F07; }
#content .typocode_ruby .ident { color: #0D0; }
#content .typocode_ruby .constant { color: #07F; }
#content .typocode_ruby .regex { color: #B66; }
#content .typocode_ruby .number { color: #FF0; }
#content .typocode_ruby .attribute { color: #7BB; }
#content .typocode_ruby .global { color: #7FB; }
#content .typocode_ruby .expr { color: #909; }
#content .typocode_ruby .escape { color: #277; }
#content .typocode_xml .normal {}
#content .typocode_xml .namespace { color: #C60; font-weight: bold; }
#content .typocode_xml .tag { color: #9FF; }
#content .typocode_xml .comment { color: #CCC; font-style: italic; border: none; margin: none; }
#content .typocode_xml .punct { color: #0D0; font-weight: bold; }
#content .typocode_xml .string { color: #C03; }
#content .typocode_xml .number { color: #FF0; }
#content .typocode_xml .attribute { color: #BB7; }
#content .typocode_yaml .normal {}
#content .typocode_yaml .document { font-weight: bold; color: #07F; }
#content .typocode_yaml .type { font-weight: bold; color: #C60; }
#content .typocode_yaml .key { color: #C60; }
#content .typocode_yaml .comment { color: #CCC; font-style: italic; border: none; margin: none; }
#content .typocode_yaml .punct { color: #0D0; font-weight: bold; }
#content .typocode_yaml .string { color: #C03; }
#content .typocode_yaml .number { color: #FF0; }
#content .typocode_yaml .time { color: #FF0; }
#content .typocode_yaml .date { color: #FF0; }
#content .typocode_yaml .ref { color: #944; }
#content .typocode_yaml .anchor { color: #944; }

#content .typocode {
  background-color:#eee;
  padding:2px;
  margin:5px;
  margin-left:1em;
  margin-bottom:1em;
}

#content .typocode .lineno {
  text-align: right;
  font-family: monospace;
  padding-right: 1em;
}

And now, by doing something like this...

<typo:code lang="ruby">
# returns date and time from a Time object
  def date_time(input)
    if input 
      input.strftime("%d/%m/%y %H:%M")
    end
  end
</typo:code>

... the result is something like this:

# returns date and time from a Time object
  def date_time(input)
    if input 
      input.strftime("%d/%m/%y %H:%M")
    end
  end

And here's some YAML:


line_item_18:
  order_id: 10
  quantity: 1
  product_id: 2
  id: 18
  unit_price: 6.99
  created_at: 2006-07-15 15:38:55.206595 +01:00

If you'd like to take advantage of this (and why would you be reading this if you wouldn't?), you can download my content.css here:

/themes/scribbish/stylesheets/content.css

Its worth pointing out that after installing the new CSS file in your scribbish directory, you should flip to another theme and back again in order to make Typo pick up the change.

Accessing Logged In Users with SaltedHashLoginGenerator

Posted by James Wilford Thu, 23 Feb 2006 23:43:00 GMT

So, I'm using SaltedHashLoginGenerator in my project. All the buzz seems to be around acts_as_authenticated at the moment, but I wasn't too impressed. It seems to lack basic functionality such as 'forgot password'. So I stuck with SaltedHashLoginGenerator, figuring if it ain't broke, don't fix it. However, I have added a few neat tricks to make it easier to use, which are described here.

Are we logged in?

So the most basic thing you'll want to do is alter the behaviour of your controller actions and your views based on whether a user is logged in or not. You might also want to use some of the user's information, for example to display their name.

By default, support for this kind of thing is rather weak. In the "user_system" module (/lib/user_system.rb), there is a "user?" method which returns true or false. You can use this in your controllers to see if a user is logged in. For example:

if user?
  launch_rocket
else
  redirect_to :controller =>  user, :action => login
end

However, if you want to actually use the user's details, or refer to the user in a view, the docs just say you should use @session['user']. Which frankly brings back nasty flashbacks of PHP for me.

Methods to my madness

So the first thing I did was to define a "logged_in_user" method in application.rb:

def logged_in_user
  @session['user'] if user?
end

This can be used in the controller like this:

@user = logged_in_user

Nice Views

All very well, but what about views? Surely we need to define the same kind of things as helpers for the views, a clear violation of the DRY principle.

Not surprisingly, the rails developers have thought of this. And they have very helpfully allowed the use of controller methods and attributes as helpers by means of the methods "helper_method" and its alias, "helper_attr". This allows me to define my application controller something like this:


require 'localization'
require 'user_system'

# The filters added to this controller will be run for all controllers in the application.
# Likewise will all the methods added be available for all controllers.
class ApplicationController < ActionController::Base
  include Localization
  include UserSystem

  helper :user
  model  :user

  helper_method :user?
  helper_attr :logged_in_user
  def logged_in_user
    @session['user'] if user?
  end

end

The 'logged_in_user' method must be here, not in the user_system module, or you'll get a NameError. I'm not sure why this is, so please leave a comment if you know.

Typo Online!

Posted by James Wilford Fri, 17 Feb 2006 18:57:00 GMT

Greetings, dear reader, and welcome to my new Typo-based blog. My name is James Wilford, and I live in Brighton (thats in England) where by day I do technical support at an ISP, and in my spare time develop websites, play with Linux, read books, and sometimes dabble with the cello. You may notice the two older stories below, these were copied from my long-defunct old site.

For those who don't know, Typo is a blogging app written in Ruby on Rails, the fantastic (relatively) new web development framework that is taking the web development world by storm.

On this site I'll be blogging about my own experiences with Ruby on Rails (or RoR for short), as I use it to develop a website...

What's the website I'm working on? Well I'm glad you asked... its basically an online mail-order record shop, specialising in selling independently released CDs and vinyl, direct from the artists. Yes, its been done before, but not based in the UK, as far as I can tell.

So join me in an adventure on Rails!