Vagrant, Ubuntu, node.js and upstart

Over the weekend, I hacked together a quick script to tail a file with node.js and nowjs.

The first time I used node, it was back in version 0.4.6 and I had to install npm manually. It was a pain in the ass to get it running at all. At the time, it seemed like npm installed things into some location that node couldn't require from.

That aside, I decided to give it another shot. Mainly because it's been known to be awesome for many reasons.

Setting up node.js on OS X

I installed it on OS X first, since I didn't need to compile it, just used their Macintosh installer. I also figured it'd be easier to run locally, than through a server. Much to my surprise, npm is now included with node. I used npm to install nowjs and I was set, almost.

Node installed

# needed to install it globally
~] sudo npm install now -g

# not sure if I needed this at this point, but doesn't hurt
~] export NODE_PATH=/usr/local/lib/node_modules

I punched in the "hello world" script.

var http = require('http');
http.createServer(function(req, res){
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(1337, "127.0.0.1");
console.log('Server running at http://127.0.0.1:1337/');
~] node example.js

~] curl localhost:1337
Hello World

Worked, like a charm.

Since this post is titled the way it is, there's more.

node.js with vagrant and chef

I have a vagrant box setup to do my development on. It's pretty basic, running a lamp stack using chef / cookbooks to set it up. If you're interested, I got the setup from this github repo.

If you're not familiar with Vagrant, it makes use of Virtual Box to enable you to setup a virtual environment, quite easily...sometimes. Vagrant has issues sometimes, when you have to reboot your computer or suspend / resume it. Mainly, it not coming back from a suspended state and having to destroy and rebuild the box. But in my experience, it's more useful than not. It can also make use of puppet or chef to help automate installation of things such as Apache, MySQL, PHP. I'm not sure what kind of things are out there for people using puppet but the guys at opscode have their own public repo of cookbooks for maintaining servers.

It took a while to get node working properly on the VM. First, I had to get node.js installed. I did it manually, first, to test things out. Then I added a cookbook to setup Vagrant. I found this post to help setup nodejs using a vagrant/chef setup.

First, I cloned this cookbooks repo and copied the nodejs folder to my cookbooks folder. I then updated my Vagrantfile to include the nodejs recipe and clamped it to version 0.6.5, just in case.

Vagrant::Config.run do |config|
  config.vm.box = "lucid64"
  config.vm.box_url = "http://files.vagrantup.com/lucid64.box"

  config.vm.forward_port("http", 80, 8888)
  config.vm.forward_port("mysql", 3306, 3306)

  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = "cookbooks"
    chef.add_recipe("vagrant_main")
    chef.add_recipe("nodejs")
    chef.json.merge!({
      :nodejs => {
        :version => "0.6.5"
      }
    })
  end 
end

I rebuilt the box to test out the script, and it worked (lucky).

# destroy box
~] vagrant destroy
# bring it back up, from scratch
~] vagrant up

I put my node.js app with nowjs somewhere on the box and tried to run it. An exception was thrown because I didn't have nowjs installed. Which reminds me, I still have to add that part to a cookbook. At this point, I just quickly installed it by ssh'ing into the vagrant box.

~] vagrant ssh
# need to install it globally, in this case (-g option)
sudo npm install now -g
cd /var/local/node-tail
node server.js /var/log/php.log
# another exception...because it couldn't find now in the path
export NODE_PATH=/usr/local/lib/node_modules

At this point, going to http://localhost:8228 (the port specified in my node app) wasn't showing my app. Long story short, I edited my Vagrantfile to forward port 8228 (localhost) to 8228 (node server port).

Vagrant::Config.run do |config|
...
  config.vm.forward_port("http", 80, 8888)
  config.vm.forward_port("mysql", 3306, 3306)
  config.vm.forward_port("node", 8228, 8228)
...
end

I had tried setting up a reverse proxy using Apache but all the different ports were screwing with nowjs. The problems were, nowjs automatically serves now.js and socket.io.js.

Because I was forwarding node.local:8888 to apache and the node server was set to run on localhost:8228, it was trying to include them from localhost:8228 and not node.local:8888. I fiddled around and got to a point where it was including now.js through node.local:8888 but nowjs was trying to include socket.io.js from localhost:8228, which wasn't accessible.

I just canned the reverse proxy and forwarded the port using the config.vm.forward_port option of Vagrant.

Starting node.js on Ubuntu with upstart

I found a few ways of setting up an upstart script to get nodejs running on boot.

It's getting late, so I'm going to cut this short. I went through a lot of trouble trying to get this to work but ended up with this script.

description "node-tail service"
author "Don <[email protected]>"

# this one didn't work properly, maybe environment wasn't setup when it was trying to do stuff
#start on startup

# this one also didn't work
# start on started mountall

# saw this in http://caolanmcmahon.com/posts/deploying_node_js_with_upstart and it works for me
start on (local-filesystems and net-device-up IFACE=eth0)

stop on shutdown

# tries to restart if it dies
respawn

# I think this is needed since my node.js app actually creates a new child process (tail)
expect fork

# I tried a few different lines here, I ended up with this one
# some solutions "export HOME=/root" but that doesn't make sense to me, especially if you specify the user as not root
# instead, I just set the NODE_PATH when I run the command
# specifying the user as www-data is a little more secure than using root
exec sudo -u www-data NODE_PATH=/usr/local/lib/node_modules /usr/local/bin/node /var/local/node-tail/server.js /var/log/php.log >> /var/log/node-tail.log 2>&1

And there you have it, a node.js app setup on an Ubuntu Vagrant box.

Btw, thanks to Pbum for recommending upstart (showing me his minecraft upstart script) and Neil Lalonde for linking me to UbuntuBootupHowto they were valuable resources :D