There are a few bash shell tricks I’ve learned lately that have all come together to assist in helping me write a script that can remotely execute ruby code on a server. The first trick is heredocs in bash:
echo << +++
This is a heredoc.
More than one line is just fine.
+++
The use of << is the start of a heredoc. It means that you want to include a multi-line string as an argument to the command (‘echo’), beginning and ending with a specific string. Any string can go after the angle brackets, so I chose “+++” since it’s unlikely to appear in my quoted text. The “+++” at the beginning of the 3rd line indicates the end of the heredoc (block of text). It has to be at the beginning.
Next up is literal heredocs:
echo << '+++'
This is a $literal heredoc!
The $dollar signs are not treated as variables,
and the bang (!) is not special.
Strangely, by simply quoting the heredoc start marker, the heredoc becomes a literal one: there will be no string interpolation or variable substitution or any other manipulation that bash normally does with strings. This is really handy when dealing with code because dollar signs and other special characters are more common.
While not strictly necessary (unless you need variable substitution, see below), I also combine with the ‘tee’ operator to make piping to other commands for preprocessing easier:
tee << '+++' | echo
This is a heredoc again.
+++
The 'tee' command simply takes standard input and sends it to standard output, no buffering. So now that I have a literal heredoc being sent to echo, what if I want to explicitly substitute certain "variables" in my text? I use sed to do the replacement:
tee << '+++' | sed "s/MYVAR/$MYVAR/g" | echo
puts "You said MYVAR!"
+++
So this might seem like a strange thing to do... I'm using a literal heredoc to AVOID variable substitution, and then I'm using sed to do variable substitution! The reason for this is that I want more control over what is substituted. When I have a large chunk of ruby code that I want to execute, I don't want to have to worry about escaping dollar signs and bang characters. Instead, I use a literal heredoc and then explicitly replace special strings with their substitutions using sed. Safe and effective.
Finally, instead of piping to echo, we can simply pipe to ssh and add 'ruby' as the last parameter to ssh:
tee << '+++' | sed "s/MYVAR/$MYVAR/g" | ssh root@myserver ruby
puts "You said MYVAR!"
+++
Alternatively, you could pipe to bash. Let's do that and send a heredoc within the heredoc!
tee << '+++' | \
sed "s/SSH_HOST/$SSH_HOST/g" | \
sed "s/ADMIN_PASSWORD/$ADMIN_PASSWORD/g" | \
ssh $SSH_LOGIN@$SSH_HOST /bin/bash
tee << '---' |debconf-set-selections
# URL of Chef Server (e.g., http://chef.example.com:4000):
chef chef/chef_server_url string http://chef.SSH_HOST:4000
# New password for the 'admin' user in the Chef Server WebUI:
chef-server-webui chef-server-webui/admin_password password ADMIN_PASSWORD
# New password for the 'chef' AMQP user in the RabbitMQ vhost "/chef":
chef-solr chef-solr/amqp_password password ADMIN_PASSWORD
# Upgrading from 1.5.4 and below.
rabbitmq-server rabbitmq-server/upgrade_previous note
---
# Install chef
aptitude -y install chef-server
+++
This last example is one that I've actually used--it remotely installs chef-server (without asking for prompts) on a Ubuntu box (Lucid).
