Capistrano SSH
A quickie this time. I use Capistrano to deploy my Rails apps. There was a gem for Capistrano 2 that added an ssh command as in:
cap production ssh
This would open an SSH connection to the production server and cd you to the current directory for the app. Super handy if you work on a lot of projects and don’t want remember what the server names and deploy paths for all of them.
The Capistrano 3 has a much different internal structure and the gem is long abandoned. Fortunately, it’s easy to recreate this functionality in a view lines of code. Create lib/capistrano/tasks/ssh.rake and drop in:
desc "ssh to the current directory on the server"
task :ssh do
on roles(:app), :primary => true do |host|
command = "cd #{fetch(:deploy_to)}/current && exec $SHELL -l"
puts command if fetch(:log_level) == :debug
exec "ssh -l #{host.user} #{host.hostname} -p #{host.port || 22} -t '#{command}'"
end
end
The fiddly bits are the cd && exec
and the -t
. -t
makes sure we
have a terminal allocated on the server. Normally, when executing
commands (as opposed to starting a shell), a terminal isn’t needed,
but in this case we’re manually starting a shell, and we need it.
SSH, and then cap
, will exit when the remote command finish. If we
just ran cd
the command would login, change directory and then
finish. Exec-ing a shell after the cd
causes SSH to want for the
command, which will only finish when we log out. Exec-ing $SHELL
instead of hardcoding bash, given us some flexibility.
It’s a simple thing, but a nice time saver.
And for bonus points, the most common reason to open a shell to the server is to play around in the console. You can easily modify this task to do that with:
desc "open rails console on server"
task :console do
on roles(:app), :primary => true do |host|
command = "cd #{fetch(:deploy_to)}/current && bundle exec rails console #{fetch(:rails_env)}"
puts command if fetch(:log_level) == :debug
exec "ssh -l #{host.user} #{host.hostname} -p #{host.port || 22} -t '#{command}'"
end
end
(You could append this to ssh.rake or create a separate console.rake.)
Note that when SSH is used to run a command, it does source your shell variables. If you use RVM or rbenv, you may need to manual set up you environment. For example, an RVM use might need:
command = "source /etc/profile.d/rvm.sh; cd #{fetch(:deploy_to)}/current && bundle exec rails console #{fetch(:rails_env)}"
Using ; instead of && will cause the cd and bundle to run even if the source fails, a quick and dirty way to make this work in cases where some servers are using RVM and some are not.
Comments