Skip to Navigation | Skip to Content

Kelli Shaver

I'm developer from Kentucky who specializes in building awesome web applications and has a passion for nonprofits and open source code.

Mass Assignment Protection, DataMapper, and Sinatra

Tip: You can use the DM Rails adapter MassAggisnmentSecurity sub-module inside Sinatra if you’d rather limit attribute access at the model level, rather than handling it in the controller (which certainly feels cleaner to me).

For example:

require 'dm-rails/mass_assignment_security'

class Status  
    include DataMapper::Resource  
    include DataMapper::MassAssignmentSecurity

    attr_accessible :account_id, :contents, :source
end

You’ll, of cours,e need to install the dm-rails gem.

Edit: Just a not eI discovered after updating my gems yesterday. You cannot have two versions of Rack installed when using the Rails gem inside Sinatra. Rails will load Rack 1.4 first, and cause Sinatra to be unable to load Rack 1.3.5.

Vesper; a Sinatra web framework

When it comes to structuring larger Sinatra apps, I’ve mostly just “rolled my own” following a basic MVC style architecture. Vesper’s aim seems to be to take some of the work out of that, and it does look cleaner than a couple others that I’ve looked at. I’m definitely going to be keeping an eye on this one and probably testing it out soon.

Sinatra Producton Server Setup

I recently had to set up a couple of web servers to handle multiple Sinatra applications in production. Going from a fresh install of the operating system (Ubuntu in this case) to fully working server, with Nginx, Passenger, Ruby, and Git-based deployment is a bit of a lengthy process, so I thought I’d take a few moments to outline the steps I took below.

A Couple of Notes:

  1. I was setting up some very lightweight applications, which didn’t use a database, so there are no database installation instructions outlined below, and the default configs for Passenger, Nginx, etc. as far as memory usage, pool sizes, and so on, were fine. If you’re deploying beefier applications, you will need to allocate resources accordingly.
  2. My server environment, in this case, was Ubuntu on an EC2 instance. All firewall configuration was done beforehand from the EC2 console. If you’re setting up on a different VPS provider, you’ll need to manually configure your firewall via iptables.

Now, on to the good bits. We’ll be jumping back and forth between our local machine and the remove server a bit, so let’s start things off….

On the SERVER

Update the OS

sudo apt-get update sudo apt-get upgrade

Set the Hostname

sudo nano /etc/hostname

Install Utilities

sudo apt-get install wget build-essential 

Install & Configure Git

sudo apt-get install git-core 
git config --global user.name = "<name>"
git congit --global user.email = "<email>"

Install & Configure Gitosis

git clone git://eagain.net/gitosis.git 
cd gitosis
sudo apt-get install python-setuptools
sudo python setup.py install
sudo adduser --system --shell /bin/sh --gecos 'git version control' --group --disabled-password --home /home/git git

Copy your SSH keypair to ~/.ssh then set permissions, initialize Gitosis and ensure proper permissions on update hooks.

chmod 0600 ~/.ssh/id_rsa 
sudo -H -u git gitosis-init < ~/.ssh/id_rsa.pub
sudo chmod 755 /home/git/repositories/gitosis-admin.git/hooks/post-update

To avoid pesky ownership permissions, let’s just add Git to the sudoers file so that we can run our post-update hooks without issue. If you’re super concerned about security, you can skip this step and and just make Git the owner of each application root directory.

sudo visudo git ALL=(ALL) NOPASSWD:ALL

Install RVM

sudo bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer) 
umask g+w
source /etc/profile.d/rvm.sh
sudo apt-get install openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion

Now give your user permissions to do stuff in RVM.

sudo chown -R [user]:[user] /usr/local/rvm

Install Ruby & RubyGems

rvm install 1.9.2 
source "/usr/local/rvm/scripts/rvm"
rvm --default use 1.9.2

Install Gems

gem install rack rake sinatra thin bundler

Install Nginx & Mod Rails

sudo apt-get install libcurl4-openssl-dev libssl-dev zlib1g-dev gem install passenger 
rvmsudo passenger-install-nginx-module

Select the first option and set install dir to /etc/nginx (purely personal preference, but it feels more consistent than having it in /opt)

Install Nginx Control Script

cd /opt 
sudo wget -O init-deb.sh http://library.linode.com/assets/604-init-deb.sh
mv /opt/init-deb.sh /etc/init.d/nginx
chmod +x /etc/init.d/nginx
/usr/sbin/update-rc.d -f nginx defaults

Since we changed the install directory, we have to edit the control scripts.

sudo nano /etc/init.d/nginx

edit PATH and DAEMON to point to the right location

Make some directories

cd /srv 
sudo mkdir www
sudo mkdir
www-logs
sudo mkdir www/<project>
sudo chmod -R 0777 www
sudo chmod -R 0777 www-logs

Set up Nginx Virtual Hosts

sudo nano /etc/nginx/conf/nginx.conf

Below is a sample nginx.conf file:

worker_processes 1;
events {
worker_connections 1024;
}

http {
passenger_root /usr/local/rvm/gems/ruby-1.9.2-p290/gems/passenger-3.0.11;
passenger_ruby /usr/local/rvm/wrappers/ruby-1.9.2-p290/ruby;
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name www.<project> <project>;
access_log /srv/www-logs/<project>.access.log;
error_log /srv/www-logs/<project>.error.log;

root /srv/www/<project>/public;
passenger_enabled on;

location /static {
root /srv/www/<project>/public;
index index.html;
}
}
}

Set up Gitosis Hosting for Repos

cd ~ 
git clone git@<server>:gitosis-admin.git
cd gitosis-admin
nano gitosis.conf

Below is a sample gitosis.conf setup:

[group <company>]
writable = <project>
members = <user>

Then commit it.

git add .
git commit -a -m "adding <project> repos"
git push

Go to your LOCAL repos!

Add remotes to repos and Push

cd ~/www/<project> 
git remote add production git@<server>:<project>.git
git push production master

Back to the SERVER!

Set up Deploy Hooks

sudo nano /home/git/repositories/<project>.git/hooks/post-update

Below is a sample post-update script:

#!/bin/sh 
git archive --format=tar HEAD | (cd /srv/www/<project>/ && tar xf -) sudo touch /srv/www/<project>/tmp/restart.txt

Make it executable.

sudo chmod 0755 /home/git/repositories/<project>.git/hooks/post-update

And now back to the LOCAL repos!

Deploy Stuff

cd ~/www/<project> 
mkdir tmp
touch tmp/restart.txt
git add .
git commit -a -m "adding restart.txt for passenger"
git push production

Back to the SERVER!

Restart Nginx

sudo /etc/init.d/nginx restart

And that should be it. You should now be good to go.

Happy Docs Rewrite & More

A few weeks ago, I posted about a little OSS CodeIgniter app I’d built for documenting APIs called Happy Docs. That code is still available, but Happy Docs has now gone in a new direction.

You can now use Happy Docs as a hosted service!

I gave the system a complete rewrite, added multi-user support, improved private projects, and gave the interface a bit of an overhaul.

The end result:

Happy’s free to use, so if you build APIs, go ahead and sign up and give it a spin.

Finally, I’d like to talk a little about the rewrite.

The initial, open source version was written in PHP. I did this primarily because deploying something with PHP is dead-simple and the software can live on pretty much any web host on the planet. For something you plan to give away, the ability to “run anywhere” is a huge benefit, in terms of reach, and in terms of support.

When it came time to start building in more functionality, however, I began to seriously re-think the approach.

Ultimately what it came down to was that:

  1. I was going to have to make a lot of changes if I wanted more integrated authoring/viewing (no separate admin interface) and user accounts, with multiple contributors per project.
  2. Since I was changing a lot of code anyway, I might as well just rewrite the thing.
  3. If I’m going to be maintaining and expanding on the app, I’d really prefer to be looking at and working with Ruby code over PHP.

That last bit mostly comes down to personal preference. I’m not a PHP-hater. I know it quite well, use it often, and I don’t mind developing in it, but when the project is mine, I’m going to use the language I prefer to develop in (Yeah, so, give it a month and maybe I’ll end up rewriting v3 in Python…).

GTalk and Ruby

Yesterday I had the need to create a small GTalk bot in Ruby. I started looking around and, to be honest, it was really hard to find any examples newer than 2009 or so. Even the gems are old.

Sure, maybe the XMPP protocol hasn’t changed that much over the years, but Ruby has, so I thought I would make a quick note about how I finally got this working.

XMPP4R-Simple won’t work in Ruby 1.9.x (and I didn’t have time to debug it)

The Easy-GTalk-Bot gem is a very simple wrapper for XMPP4R and it worked fine for the most basic of bots, but it couldn’t handle the asynchronous nature of what I was building. The multi-threading would break down and the connection would drop.

Blather looked promising, but also seemed like overkill for what I needed and connection handling just looked like too much work (not really difficult, but overly involved for what should be necessary in this particular instance).

The gem you’ll want to use is just the plain and simple XMPP4R.

Here’s the basics to get you started. The below script will authenticate to GTalk, automatically accept any incoming “add contact” requests, and echo received messages back to the user. — Totally useless, but functional.

# gtalk.rb
require 'rubygems' require 'xmpp4r' require 'xmpp4r/roster' @username = 'you@gmail.com' @password = 'seekrit' @jid = JID::new(@username) @client = Client.new(@jid) @client.connect @client.auth(@password) @client.send(Presence.new.set_type(:available)) def roster @roster ||= Roster::Helper.new(@client) end roster.add_subscription_request_callback do |_, presence| inviter = JID::new(presence.from) roster.accept_subscription(inviter) invite(JID::new(inviter)) end @jabber_client.add_message_callback do |message| unless message.body.nil? # do stuff.... text = "You said '#{message.body}'" response = Message::new(JID::new(message.from), text) @client.send(response) end end Thread.stop

And here’s how you daemonize it with the Daemons gem.

# im.rb
require 'rubygems' require 'daemons' Daemons.run('gtalk.rb', { :app_name => 'myIMBot', :monitor => true, :log_output => false } )

You can now run the script with ruby im.rb start, ruby im.rb restart, and ruby im.rb stop. If you want to save error output, just enable logging. As an added bonus, Monit will now watch the script and restart it if it crashes.