We hope by now that you’ve had a chance to check out Moonshine and maybe even give it a go on your own server. We’ve been using Moonshine to deploy our customer’s servers for a while now, and we’ve started extracting little pieces of manifests into Moonshine plugins. I’m going to show you how to add Moonshine plugins to your manifest, and give you the lowdown on writing your own.
Installing and using plugins
Moonshine plugins are installed just like any other Rails plugin:
script/plugin install git://github.com/railsmachine/moonshine_ssh.git
Then add a few lines to your application’s manifest-
app/manifest/application_manifest.rb
, by default.
configure( :ssh => { :allow_users => ['rails'] } )
plugin :ssh
recipe :ssh
Not all plugins will need to be configured before they’re used and the recipe name doesn’t need to be the same as the plugin name- in fact, a plugin could provide as many recipes as you like. The next time you deploy your application, Moonshine will run the recipes you specified from the installed plugins.
Writing your own
Now here’s how you can really shine while you ‘shine. If you’ve added functionality in your manifest that you think others could use, create a new Moonshine plugin for it and put it up on github. Here’s how we do it.
Generate the plugin stub
Start by running the plugin generator from your application directory. Just switch out “ssh” for
whatever it is you’re plugin-ifying.
script/generate moonshine_plugin ssh
This creates the following structure in the vendor/plugins
directory of your
app:
|--moonshine_ssh
|-- README.rdoc
|-- moonshine
| `-- init.rb
|-- lib
| `-- ssh.rb
|-- spec
|-- spec_helper.rb
|-- ssh_spec.rb
Add recipes
The lib/
directory is where we’ll put puppet recipes. For our SSH plugin we
add this:
def ssh(options = {})
package 'ssh', :ensure => :installed
service 'ssh', :enable => true, :ensure => :running
file '/etc/ssh/sshd_config',
:mode => '644',
:content => template(File.join(File.dirname(__FILE__), '..', 'templates', 'sshd_config'), binding),
:require => package('ssh'),
:notify => service('ssh')
end
This is straightforward- we’re making sure that SSH is installed, that the
service is running and will start on system boot. We’ll create a templates/
directory with an ERB template called sshd_config
. It will be rendered and
saved as /etc/ssh/sshd_config
. The SSH service will be notified to restart
when it changes. The template will have lines like this sprinkled throughout:
LoginGraceTime <%= options[:login_grace_time] || 30 %>
PermitRootLogin <%= options[:permit_root_login] || 'no' %>
The options come from our configure
line in the first example. If you
recall, we defined a property called :ssh
, which we set to a hash. This hash
will be passed to the ssh
recipe by Moonshine. This little convention helps
keep the custom configuration of your server separate from its list of parts.
Do the safety dance
Now we’ve got a working plugin that can manage your SSH settings. Just one thing- what if
someone configured ssh like this?
configure(:ssh => {:port => 'two'})
This clearly isn’t valid and we don’t want them to be locked out, so let’s test the file before we update it.
def ssh(options = {})
package 'ssh', :ensure => :installed
service 'ssh', :enable => true, :ensure => :running
file '/etc/ssh/sshd_config.new',
:mode => '644',
:content => template(File.join(File.dirname(__FILE__), '..', 'templates', 'sshd_config'), binding),
:require => package('ssh'),
:notify => exec('update_sshd_config')
exec 'cp /etc/ssh/sshd_config.new /etc/ssh/sshd_config',
:alias => 'update_sshd_config'
:onlyif => '/usr/sbin/sshd -t -f /etc/ssh/sshd_config.new',
:refreshonly => true, # do nothing until notified
:require => file('/etc/ssh/sshd_config.new'),
:notify => service('ssh')
end
There you have it. We upload the new configuration to a temporary location and replace the previous one only if the syntax check returns 0. This plugin is ready to be put online for all to enjoy.
The first plugins
So far, we’ve released plugins for managing iptables, SSH,
and god. Look for more in the coming days. If you’ve written one, let us know about it in the comments!
Update: Check out the list of plugins and add your own on the plugin wiki page.