What is mina?

mina is a deployment tool, which utilizes a rake-based ruby DSL very much like capistrano. Although capistrano is usually considered as the way to go when deploying rails, or other rack-based, applications, mina comes with one essential benefit, which lured me away from capistrano:

  • mina compiles the tasks, which are about to be run, into a bash script which is run on the server with only one open ssh-connection.

Since this could mean a substantial increase in speed it allows you to deploy to a testing system without the usual cigarette break; which are three to five minutes, that are really inconvenient if you don’t smoke.

How do i use it?

The authors Rico Sta. Cruz and Michael Galero have done a really good job explaining the basic functionality of mina in its README and almost every question, pertaining to usage and options, can find its answer in the extended documentation on their site.

So i will only be covering the basic need-to-know of mina in this entry and focus on its application to git repositories.

Similar to capistrano the deployment configuration is found in config/deploy.rb under rails’ root path. A basic configuration can be created with a binary call similar to capistranos capify:

mina init

After you’ve changed the domain and repository option in lines 13-17, you’re good to go for your first deploy:

set :domain, 'foobar.com'
set :deploy_to, 'var/www/foobar.com'
set :repository, 'git://...'
set :branch, 'master'
  • But these setting assume you’re using a git repository which is public accessible. You can, of course, change these to settings which allow for password-based authentication. Since many private projects use asynchronous authentication with a public-private key system (like RSA), we’ve reached the problematic situation i stumbled upon when trying to use mina for the first time.

A little research revealed, that there is currently no option in mina, which allows supplying a ssh-key for cloning the git repository - although you can supply one for the connection to the deploy-server.
But git itself comes to the rescue!
git supports setting an environment variable, which points to a script that is supposed to contain the call to ssh. And this allows the integration of the ssh-key via the -i switch.

The script, which will be used in our little mina deployment config, is a basic oneliner:

ssh -i path/to/file -o ’StrictHostKeyChecking no’ $1 $2
  • $1 and $2 are variables passed to the script via the command line and required by git for its options (like the host).

Since we are using ruby we are building this oneliner via a simple method:

def build_ssh_cmd
  string = "ssh "
  git_ssh_options.each_pair do |switch,value|
    string << "#{switch} \"#{value}\" "
  end
  string << "$1 $2"
end
  • git_ssh_options holds the previously defined options, which we want to inject into the ssh-call, to make it possible to use ssh-key-authentication.

I’m quite lazy, especially when it comes to file path, so we will be using a little path magic. The file_to helper-method creates file paths for files i want to push to the deploy-server.

def file_to(file)
  File.join(deploy_to, deploy_set_loc, file)
end
  • deploy_to is the basic path, to which our app will be deployed.
    it is expected by mina.
  • deploy_set_loc is a relative path, where settings-related files are located. I’m usually going with
    set :deploy_set_loc, "/shared/config/deployment/"

Dependent on these settings, git_ssh_options will be defined in this way:

set :git_ssh_options, {
  "-i" => "#{file_to("ssh_key")}",
  "-o" => StrictHostKeyChecking no
}

Of course one could pass their own options, if they would need to, because the build_ssh_cmd supports it.

At this point we’ve created our own ssh-call which allows for a specific ssh-key to be used. But this key needs to be placed on the deploy-server. Of course one could just copy it in place, by using scp or a similar tool, but this would destroy the convenience one gets by using one deployment-script, wouldn’t it?

Since ssh-keys (and configuration files) are usually pretty short, we can just paste it into the bash script, which will be generated by mina. These settings will take care of it:

set :git_ssh_file, File.join(File.dirname(__FILE__), deployment/ssh_key)
set :git_ssh_key, File.read(git_ssh_file)
  • The first one sets the path to the ssh-key, while the second will read the file into memory.

Now a custom task called :provider will do the rest. It is defined like this:

task :provider do
  queue! %[mkdir -p "#{File.join(deploy_to, deploy_set_loc)}"]
  queue! %[echo "#{git_ssh_key}" > #{file_to(ssh_key)}]
  queue! %[chmod g-rwx,o-rwx "#{file_to(ssh_key)}"]
  queue  %[echo "-----> Integrated git ssh key."]
  queue  %[echo "-----> \\$GIT_SSH is set to $GIT_SSH"]
  queue! %[echo ’#{build_ssh_cmd}’ > #{file_to(clone.sh)}]
  queue! %[chmod u+x,g+x "#{file_to(clone.sh)}"]
  queue  %[echo "-----> built the git-ssh command."]
end
  • This task will create the deployment directory on the deploy-server, which is the directory where we will store the ssh-key.
  • It will also store the ssh-key itself and set the proper permissions.
  • And it will build the ssh command and write it to a file called clone.sh in our deployment directory.

So now all we have to do, is run it, correct?
Wrong.
Git expects us to actually tell it, which script to use instead of its own ssh-command generation.

We will do this in the :environment task, which is a task generated by mina init and is used to set environment related info. And this is what we will do:

task :environment do
  queue! %[export GIT_SSH="#{file_to(clone.sh)}"]
end
  • This will set the $GIT_SSH variable to a path, pointing to the ssh script.

After this, we can safeley run mina setup followed by mina deploy and be happy.

A whole example deploy.rb containing these settings and changes can be found in this gist.