RailsConf Europe

Finally, I’ve gotten around to booking up everything for RailsConf Europe — flights, hotel, the conference itself. I’m signed up for the all-day charity tutorial because, well, it’s just a fundamentally good idea and well worth supporting/encouraging. Though I hope Dave doesn’t go quite as overboard about charitable donations as he did at RailsConf — I’m paying a lot of money to go and talk to people about Rails, not to be lectured at about the money I give to charity! I already give as much as I feel I can afford, thank you very much!

Anyway.

I’ve been plotting my plans for the time I’m in Berlin on Google Maps, messing around with creating my own map:


View Larger Map

I’ll be arriving at the hotel around 14:00 local time, I reckon, and I’m signed up for Bratwurst on Rails. I’m open to making other plans if anybody wants to meet up on the Sunday afternoon?

Sentences with grammar and sense like stuff

As I ranted on Twitter earlier this morning, I was reading a document from a rather large, well-established payment service provider talking about a massive, monolithic, upgrade they were about to undertake. The documentation I was reading was about the potential impact on applications which use their service. I’ll quote a portion for you:

The remote administration features continues however because the underlying architecture has changed the responses our system returns now be as follows.

It doesn’t make any more sense in context, but revealing the context may reveal the offender. :-) I’m interpreting it as:

The remote administration features will continue to be supported as-is, however because the underlying architecture has changed, the responses our system return have been modified as follows:

Now I’m not particularly criticising the person who wrote the document; obviously their skills are specialised towards the technical end of the spectrum and that’s perfectly reasonable. Mine are too. However, what’s not acceptable is that a company let this out their door as being representative of the company. Now I happen to know this particular company also produces several print publications and has a staff of editors. In my humble opinion, every document that comes out of an organisation should be proofed for style, particularly if that organisation has the professional capability in-house!

OK, rant over. I’ll now allow you to pull apart this post, pointing out all the grammatical errors, in the comments. :-)

Social Networking

I seem to have been sucked into Facebook. I don’t know quite what it is, but it does seem to be addictive. My theory is that it’s the news feed, telling you what new things your friends are up to and who/what they’ve discovered on the site, which allows you to discover new things too. That’s definitely a concept I’ll be incorporating into a couple of web apps I’m currently working on. :-)

Anyway, you can find my Facebook profile here: Graeme Mathieson’s Facebook Profile. And for business networking, you can find my LinkedIn profile here: Graeme Mathieson’s LinkedIn profile. Feel free to connect to me on either if you feel you know me.

Double space vs. Single space

One of the companies I’m contracting for just now (which is actually nearing the stage where I might be able to talk about some of our fun, exciting work over the past several months!) has an editorial aspect to the business. They receive manuscripts from authors and they are passed through an Editor who copy-edits and proofs them before they go much further.

It turns out that the editorial policy in the office is to have a single space between sentences instead of a double space. I personally always use a double space and thought that was the accepted standard — you learn something new every day! Like me, most of the authors submit their manuscripts with double spaces. We were talking about ways to automate the replacement (mostly with find-and-replace in Word for now), but a thought occurred to me. These documents are destined solely for the Internet, and in mostly cases will only be rendered into HTML for a browser.

So, I’m wondering: does it matter? Do web browsers respect the number of whitespace characters between sentences when they render? Or do they just think “hmm, it’s whitespace, I’ll put in a fixed bit of whitespace”? Or, even better, since it’s a stylistic convention, is it controllable through CSS?

Yeah, this is a lazyweb request. :-)

Update: OK, let’s test:

Single space. Double space. Delimiter.

Integrating capistrano with SMF

I’ve got a new application I’m in the process of deploying in order to demo for a client (no, it’s not ready for everybody else to have a nosy at just yet!) and figured I’d take the opportunity to learn two things:

And I think I’ve pulled the right bits together to make it work rather well. If I do say so myself. :-)

First up I got the basic project running and deploying. I decided to work with, rather than against, capistrano as much as possible. So that meant using script/spin and script/process/* instead of messing around with trying to port mongrel_cluster recipes to cap2. So I created a script/spin with the following:

#!/usr/bin/env bash

/u/apps/example/current/script/process/spawner mongrel -p 9000 -i 3 -a 127.0.0.1

which makes the spawner start three mongrel processes, listening at 9000, 9001, & 9002, on localhost. Dead simple. Add that to your subversion repository and commit. Now let’s create a default capistrano configuration for the project:

mathie@lagavulin:example$ capify .
[add] writing `./Capfile'
[add] writing `./config/deploy.rb'
[done] capified!

Before I go any further, let’s have a wee aside about my current capistrano setup. I still have applications that require Capistrano 1.4.x (they’re using third party plugins which haven’t yet been ported), so I need to have access to both versions in my environment. I’ve done this by having both versions of the gem installed. Then in my bash environment, I have the following:

alias cap1="`which cap` _1.4.1_"
alias cap2="`which cap`"
alias cap="echo 'Please explicitly choose cap1 or cap2.'"

So running cap1 will pick capistrano version 1.4.1, and cap2 will run the latest installed version. Another useful snippet picked up from the Harnessing Capistrano tutorial — thanks Jamis!

Anyway, on with the show. Let’s create a very basic deployment which deploys the application to ‘example.rubaidh.com’ running as the user ‘deploy’:

# Basic configuration
set :application, "example"
set :repository,  "http://svn.rubaidh.com/#{application}/trunk"
set :host, "example.rubaidh.com"
set :user, "deploy"
set :scm_username, ENV['USER']
set :deploy_via, :remote_cache
set :use_sudo, false

role :app, host
role :web, host
role :db,  host, :primary => true

# Specify some dependencies
depend :remote, :command, :gem
depend :remote, :gem, :mongrel, '>=1.0.1'
depend :remote, :gem, :hpricot, '>=0.6'
depend :remote, :gem, :rmagick, '>=1.15.7'
depend :remote, :gem, :rake, '>=0.7'
depend :remote, :gem, "bcrypt-ruby", '>=2.0.2'
depend :remote, :gem, :BlueCloth, '>=1.0.0'

# Clean up after ourselves, so we don't leave too many old releases lying
# around.
after :deploy, "deploy:cleanup"

A pretty simple configuration, but it shows off a couple of new features in Capistrano 2, the dependency checking (depend) and the new hooks system (after). The latter, in particular, is a neat wee trick, I reckon. After it’s finished a deployment, it’ll automatically clean up old releases, only leaving around the last 5. Kudos to Craig for introducing that to me after we ran out of disk space on one production machine with 200+ old deployments lying around!

Doing a deployment is pretty simple:

mathie@lagavulin:nang$ cap2 deploy:setup
  * executing `deploy:setup'
[ snip ]
    command finished
mathie@lagavulin:nang$ cap2 deploy:cold
  * executing `deploy:cold'
[ snip ]
    command finished

Which will do the usual setup tasks, deploy the system, run any pending migrations and start up the servers. That’s us done for part one. Now to get SMF to manage the mongrels for us. First of all, we need to create a service manifest. Create the following file on the server:

<?xml version='1.0'?>
<!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
<service_bundle type='manifest' name='example'>

  <!-- New service, called application/mongrel/nang -->
  <service name='application/rails/example' type='service' version='0'>

    <!-- Not enable by default when we're imported -->
    <create_default_instance enabled='false' />

    <!-- There can only be one! -->
    <single_instance />

    <!-- Dependent upon the local filesystem having been started -->
    <dependency name='fs' grouping='require_all' restart_on='none' type='service'>
      <service_fmri value='svc:/system/filesystem/local' />
    </dependency>

    <!-- Dependent upon the network having started up, since we bind to localhost -->
    <dependency name='net' grouping='require_all' restart_on='none' type='service'>
      <service_fmri value='svc:/network/loopback'/>
    </dependency>

    <!-- Multi-user is dependent upon us starting up. -->
    <dependent name='multi-user' restart_on='none' grouping='optional_all'>
      <service_fmri value='svc:/milestone/multi-user'/>
    </dependent>

    <!-- Apache depends on us starting up, since we are its backend -->
    <dependent name='apache2' restart_on='none' grouping='optional_all'>
      <service_fmri value='svc:/network/http:cswapache2' />
    </dependent>

    <!-- Environment -->
    <method_context working_directory='/u/apps/example/current'>
      <method_credential user='deploy' group='deploy' />
      <method_environment>
        <envvar name='PATH' value='/usr/bin:/bin:/opt/csw/bin'/>
        <envvar name='RAILS_ENV' value='production' />
      </method_environment>
    </method_context>

    <!-- Start and stop methods. -->
    <exec_method name='start'   type='method' exec='/bin/nohup /u/apps/example/current/script/spin' timeout_seconds='60' />
    <exec_method name='stop'    type='method' exec='/u/apps/example/current/script/process/reaper -a kill' timeout_seconds='60' />
    <exec_method name='restart' type='method' exec='/u/apps/example/current/script/process/reaper' timeout_seconds='60' />

    <!-- Authorisation -->
    <property_group name='general' type='framework'>
      <propval name='action_authorization' type='astring' value='rails.applications' />
      <propval name='value_authorization'  type='astring' value='rails.applications' />
    </property_group>
  </service>
</service_bundle>

This probably deserves some explanation. We’re creating a service manifest for the service with the FMRI of svc:/application/rails/example. We’re saying that it shouldn’t be enabled by default when it’s imported, and that there can only be one instance of it running at a time. Next we say what the dependencies are, and what services are dependent upon it (Apache, mostly). We configure its environment (working directory, user, path and RAILS_ENV). The methods for starting, stopping and restarting the service are all cargo-culted directly from the capistrano 2 default deployment recipe. Finally, there’s a wee bit of magic: We are telling the SMF framework that anybody who has been granted the rails.applications authorisation is allowed to stop and start the service, so you no longer need to be root to restart it!

Once you’ve created that file on the server, import it and check that it’s been done correctly:

mathie@example:~$ pfexec svccfg import smf.xml
mathie@example:~$ svcs example
STATE          STIME    FMRI
disabled       20:54:15 svc:/application/rails/example:default

Excellent! Now we have to do a little extra fiddling to get the authorisation to work. Add the following to the end of /etc/security/auth_attr:

rails.applications:::Manage Rails applications::

And the following to the end of /etc/user_attr:

deploy::::type=normal;auths=rails.applications

This declares the authorisation and assigns it to the deploy user. We can check that it’s been done correctly by running:

mathie@cardhu:~$ auths deploy
rails.applications,[ snip ]

Finally, we need to modify the capistrano deployment slightly. But before we do that, make sure and stop the app servers with the old configuration so nothing gets confused:

    mathie@lagavulin:example$ cap2 deploy:stop

Remove the set :use_sudo, false line from config/deploy.rb because we are going to want to use the sudo mechanism, though not for launching sudo. Then append the following:

set :fmri, "application/rails/#{application}"
set :sudo, 'pfexec'

namespace :deploy do
  task :start, :roles => :app do
    invoke_command "/usr/sbin/svcadm enable #{fmri}", :via => fetch(:run_method, :sudo)
  end

  task :stop, :roles => :app do
    invoke_command "/usr/sbin/svcadm disable #{fmri}", :via => fetch(:run_method, :sudo)
  end

  task :restart, :roles => :app do
    invoke_command "/usr/sbin/svcadm restart #{fmri}", :via => fetch(:run_method, :sudo)
  end
end

This overrides the default start, stop and restart tasks for the deployment scenario to use pfexec (to gain the appropriate authorisation) and svcadm to control the service. Run:

        mathie@lagavulin:example$ cap2 deploy:start

to make sure it works. You can verify it works by looking at svcs on the server:

mathie@cardhu:~$ svcs -p example
STATE          STIME    FMRI
online         20:56:08 svc:/application/rails/example:default
               20:56:07     5953 mongrel_rails
               20:56:07     5956 mongrel_rails
               20:56:08     5959 mongrel_rails

We have a running app. :-) Satisfy yourself that it’s all working OK by doing a full deploy (cap2 deploy) then checking that the pids listed in the svcs -p output have changed and that your app has updated. Finally, reboot the machine to check that it all comes back up again afterwards? It does? How excellent is that? :)

Best Practice with sudo

I just found this lecture in some documentation I’d been writing for a client. Clearly I was running through an install, documenting it as I go along, and was filling in time while something happened. Anyway, I thought I’d share it here:

As a side note, before I go on, let’s have some best-practice discussion about doing things as root. Since you can’t log in directly as root on Ubuntu installs, you always log in with your own user name and use sudo to gain root access. This way we get a log entry, along the lines of:

Apr 6 09:38:36 cluedo sudo: cl_mustard : TTY=pts/0 ; \
PWD=/home/library ; USER=prof_plum ; \
COMMAND=/bin/kill --with lead_pipe mrs_white

So we see:

  • When something happened (April 6th, at 9:38AM).
  • Who did it (cl_mustard).
  • Where they were (/home/library).
  • Who they masqueraded as (prof_plum).
  • The command they ran (/bin/kill –with lead_pipe mrs_white).

In order for this to work, I need to ban the use of the following two commands:

  • sudo -s
  • sudo su -

which I often used to see in the Tardis logs. When you do this, the system logs no longer show what you were up to so we lose our audit trail. Unfortunately, it’s pretty much impossible to effectively stop folks doing this, so I’m just saying: don’t do it! Always do sudo -u <user> <command> to make it explicit what you’re doing!

On the downside, there are also some situations where it’s necessary to do sudo -s – for example when you want to look at file where you don’t have read permission in the directory, but you can’t remember the name of the file!)

So there you go. That’s how I believe you should use sudo. One of these days I ought to figure out how to make the RBAC stuff in Solaris log stuff in a similar manner. I seem to recall getting as far as running bsmconv & rebooting, then getting distracted by something else…

RailsConf 2007

Well, that’s me packed, I hope. I’m just grabbing a coffee or three before I start my journey out to Portland for RailsConf 2007! The laptop has the latest Peepcode screencast: Javascript with Prototype.js; and a couple of the newest Pragmatic Programmers’ books: Release It! and Programming Erlang (not that I’m thinking seriously of switching to Erlang any time soon, it just sounds interesting, and I do like to learn a new language every year or so!). I’ve got a pile of paper books with me for when the laptop batteries give out.

The journey will be about 10.5 hours according to respective local wall clocks, but 17.5 hours elapsed time. I get in to Portland International (PDX) at 22:02 and, hopefully, can figure out the MAX-Rail system to get to my hotel, the Inn at the Convention Centre (which, I get the impression, is pretty basic, but since I’m only planning on sleeping & washing there, I’m sure it’ll do!), then collapse and hope I wake up sometime in the next 2 days! Then it’s just the strange local culture I’ll have to adjust to. :-)

Hrm, I wish O’Reilly had produced a PDF or something of the schedule, along with the abstracts. I would like to have printed that off and read it on the journey. If I’d thought about it further in advance, I could have taken the data from the web site and done so myself, but alas, no. Lazyweb request: I don’t suppose somebody else already has all the conference abstracts in one flat file that I could chown?

I’ve just checked out the weather; apparently it’ll be about 29°C today! Maybe I should have packed shorts instead of my kilt… (Umm, yeah, I don’t know if I’ll get the opportunity to wear it — that all depends if I’m invited to the cool parties! — but I do have my kilt with me.)

Anyway, all this is just my way of saying: Yay! It’s finally RailsConf and I’m on my way!