Day-to-day Babushka

posted by David Goodlad

Babushka may be described as a tool for 'sysadmin', but its usefulness doesn't stop at managing servers. I use Babushka to manage the software and configuration of my laptop, and so should you.

What is Babushka?

Babushka is, as @ben_h describes it, a tool for "test-driven sysadmin":

The idea is this: you take a job that you'd rather not do manually, and describe it to babushka using its DSL. The way it works, babushka not only knows how to accomplish each part of the job, it also knows how to check if each part is already done. You're teaching babushka to achieve an end goal with whatever runtime conditions you throw at it, not just to perform the task that would get you there from the very start.

Using a straightforward Ruby DSL, you describe how to detect the completion state of a given 'dep', and how to meet that dep.

dep 'ip forwarding enabled' do
  met? {
    File.read('/proc/sys/net/ipv4/ip_forward') == '1'
  }
  meet {
    `echo 1 > /proc/sys/net/ipv4/ip_forward`
  }
end

Why Use Babushka on Workstations?

When you set up a new computer, whether it's your primary workstation or a lightly-used travel laptop, it's useful to keep a record of what you've done: "install version X of application Y", "configure obscure setting", and so on. Later on, you can use such a record to help you remember what you've done, to help others, or to completely rebuild your system from scratch with minimal effort.

I've recently gone through that last one: I bought a new laptop and decided I wanted to start fresh. Since I'd been using Babushka to install and configure the majority of my software, building the new machine took very little effort. I updated a few download links to point at the latest versions, pruned a bunch of apps that I'd never use again, then ran babushka orion (orion is the name of my laptop, and of the dep that is configured to require everything that I want on it).

... 20 minutes later ...

Aside from a few small changes that I'd planned to make as soon as I had the chance to start fresh, my laptop was all setup and ready to go!

Getting Started

Now that you're convinced that this could be a good idea, you should probably give it a try!

I'll assume that you're running on a platform with built-in managed package support in Babushka:

  • OSX 10.5+ (uses Homebrew)
  • Debian, Ubuntu, or other apt-based Linux distributions

Installing Babushka

The easiest way to get Babushka up and running is to follow the instructions:

bash -c "`curl babushka.me/up`"      # OSX has curl
# or...
bash -c "`wget -O - babushka.me/up`" # Most linux distros have wget

Meeting Deps

Once Babushka is installed, you can use it to meet deps. The meaning of meet depends on how the dep is defined. A dep which installs software would probably be considered met if an appropriate binary can be found in your $PATH. A dep that configures an entry in the /etc/hosts file would, obviously, be considered met if there was a matching line in that file.

To meet a dep, you simply run

babushka [source]:[depname]

Your own deps are in a source called personal. The personal source is the default, so to meet a dep that you've written yourself you can run

babushka [depname]

Writing Your Deps

Babushka installs a number of base deps that it uses to configure itself, but your own deps should go in ~/.babushka/deps/. If this directory doesn't exist, you should create it. I use git to track the changes that I make to my personal deps, which I highly recommend doing. Simply run git init in your deps directory and you'll be ready to go.

You're free to write deps from scratch, but for many common cases there are 'templates' that come with Babushka. Some useful built-in templates are

  • managed: Installs software via the system's package manager. As described above, Homebrew on OSX, apt on Linux
  • app: Installs OSX app bundles
  • installer: Installs OSX .pkg/.mkpg installer files
  • src: Installs software from source, by default using the typical configure, make, make install steps.

There are others, and it's easy to write your own (see my ttf template for example).

Let's write a dep that installs the tree package from your system's package manager. Put the following into a file with the extension .rb (maybe packages.rb or system.rb?) in your deps directory:

dep 'tree.managed'

Now if you run babushka list, you'll see something like:

$ babushka list tree
√ Loaded 38 deps from /usr/local/babushka/deps.
√ Loaded 123 and skipped 1 deps from /Users/dave/.babushka/deps.

# personal (local) - /Users/dave/.babushka/deps
# 1 dep matching 'tree':
babushka 'personal:tree.managed'

Since the personal source is the default, we can meet the dep like so:

$ babushka tree.managed
√ Loaded 38 deps from /usr/local/babushka/deps.
√ Loaded 123 and skipped 1 deps from /Users/dave/.babushka/deps.
tree.managed {
  homebrew {
    homebrew binary in place {
      homebrew installed {
      } √ homebrew installed
    } √ homebrew binary in place
    build tools {
      llvm in path {
        xcode tools {
          'gcc', 'g++', 'autoconf', 'make' & 'ld' run from /usr/bin.
        } √ xcode tools
      } √ llvm in path
    } √ build tools
  } √ homebrew
  system doesn't have tree brew
  meet {
    Installing tree via brew {
      ==> Downloading ftp://mama.indstate.edu/linux/tree/tree-1.5.3.tgz
      File already downloaded and cached to /Users/dave/Library/Caches/Homebrew
      ==> /usr/bin/cc -O3 -w -pipe -o tree tree.c strverscmp.c
      /usr/local/Cellar/tree/1.5.3: 4 files, 88K, built in 2 seconds
    }
  }
  √ system has tree-1.5.3 brew
  'tree' runs from /usr/local/bin.
} √ tree.managed

Collecting Deps

I have a file in my deps repository called orion.rb. Orion is the hostname of my Macbook Pro laptop. In this file, I have a couple of high-level deps that don't actually have met/meet blocks. Instead, they simply require other deps. It looks something like this:

# Complete setup for my Macbook Pro, 'orion'

dep 'orion' do
  requires 'orion osx apps installed'

  requires 'macvim',
           'tmux',
           'ack.managed',
           'tree.managed'

  requires 'rvm'
  requires 'nvm'
end

dep 'orion osx apps installed' do
  # Social, Web, Media etc.
  requires 'Google Chrome.app',
           'Echofon.app',
           'Skype.app',
           'LimeChat.app',
           'Airfoil.app'
  # ... and lots more
end

Whenever I write a dep that I want to ensure will be met on my laptop, I add it to the list of requirements for these high-level deps. Then, instead of telling Babushka to meet low-level deps directly, I meet my top-level orion dep. This ensures that the high-level dep is an accurate representation of the system as it stands, and that I can quickly and easily rebuild the OS should I do something like buy a new machine.

Common Pitfalls

When writing your own deps based on the built-in Babushka templates, you'll sometimes run into things that don't quite fit the expected behavior. The most common cases involve software that gets installed into non-standard places.

Missing Binaries

If you're trying to install a package with the managed, src, app or installer templates, they expect that installing will result in a binary being installed in your path with the same name as the package. For example, htop.managed by default is considered met if there is a binary called htop in your path.

To override the check for binaries, use the provides method:

dep 'gnu-typist.managed' do
  # In Homebrew, GNU Typist is in a formula called gnu-typist, and installs
  # a binary named gtypist.
  provides 'gtypist'
end

dep 'freeimage.managed' do
  # The library freeimage doesn't provide any binaries
  # The template will still check that the package is installed, though
  provides []
end

dep 'erlang-nox.managed' do
  # The erlang-nox package installs two important binaries, so we should
  # verify that both are present
  provides 'erlc', 'erl'
end

provides isn't some special magic method in Babushka. Instead, it's a convention adopted by many of the templates that in their definition they call accepts_list_for :provides. This tells Babushka that deps implementing that template can override the default list, like I've shown in the above examples. If you're interested (and you should be once you start writing lots of deps and your own templates), have a look at the source for the managed template.

Non-Applications

When you use the installer template to install OSX pkg/mpkg files, you're not always installing an application into /Applications. Instead, you might be installing a prefpane, or a kernel extension. Overriding met? is usually the best thing to do in these cases. For example:

dep 'KeyRemap4MacBook.installer' do
  source 'http://pqrs.org/macosx/keyremap4macbook/files/KeyRemap4MacBook-6.9.0.pkg.zip'
  met? {
    # Test that the prefpane is in the right place.
    '/Library/PreferencePanes/KeyRemap4MacBook.prefPane'.p.exist?
  }
end

More Help

This article merely scratches the surface of what you can do with Babushka. If you'd like to learn more, or run into issues, there are a few places to turn.