jtimberman's Code Blog

Chef, Ops, Ruby, Linux/Unix. Opinions are mine, not my employer's (CHEF).

Recipe for Building Emacs

It is no secret that I use GNU Emacs as my default text editor. It is perhaps less evident but no less relevant that I use Emacs 24. I really like the built-in color theme support and the package management system for getting the various modes I like to use.

Recently, I revamped my Emacs configuration. This post isn’t about that topic. Instead, this post is about how I made sure that all the systems I want to use Emacs on have the latest version available.

Unfortunately, Emacs 24 is still unreleased, so it is not available as the default package on the distributions I use for my personal systems (Ubuntu/Debian flavors). I wrote a recipe to install Emacs from source. This is easy enough to do, but since I already automate everything on my home network with Chef, it was a natural fit for a new recipe. I simply added this to my local emacs cookbook, in recipes/source24.rb.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
srcdir = "#{Chef::Config[:file_cache_path]}/emacs-source"

%w{ git-core build-essential texinfo autoconf libncurses-dev }.each {|prereq| package prereq}

git "#{Chef::Config[:file_cache_path]}/emacs-source" do
  repository "git://git.savannah.gnu.org/emacs.git"
  action :checkout
end

bash "build emacs24" do
  cwd srcdir
  creates "#{srcdir}/src/emacs"
  code <<-EOH
    ./autogen.sh && \
    ./configure --without-x && \
    make bootstrap && \
    make 2>&1 >| make-#{node.name}-#{node['ohai_time']}
  EOH
end

execute "install emacs24" do
  cwd srcdir
  command "make install 2>&1 >| make-#{node.name}-#{node['ohai_time']}"
  creates "/usr/local/bin/emacs"
  only_if "#{srcdir}/src/emacs --version"
end

Then I updated my base role to replace recipe[emacs] with recipe[emacs::source24] and ran Chef. It took about 25 minutes to do the build, but now I have the same version of Emacs everywhere, and there was much rejoicing.

And yes, you’re absolutely right, I could just build a package and install that. However, I don’t want to set up and maintain a package management repository for my small network, as easy as that may be.

My OS X systems are a special case because I’m using Homebrew, but the homebrew cookbook does not [yet?] support install-time options, and I didn’t spend the time adding support for building the OS X Emacs w/ cocoa support from git. When I tackle that, I’ll make another post, so stay tuned!

Disable AirDrop in Mac OS X Lion

Mac OS X Lion introduced a new nifty feature called AirDrop. This allows users on a local network to drag and drop files to each other with Finder.

While it seems that this would be useful, there are security implications. After looking through Google Search Results on the topic, I found some un-helpful information in a random forum post (unsurprising). A little more review of the search results resulted in finding the actual defaults(1) command to do so:

defaults write com.apple.NetworkBrowser DisableAirDrop -bool YES

Naturally, I put this in a Chef recipe and applied it on all my Macs post haste:

1
2
3
4
5
6
  mac_os_x_userdefaults "Disable AirDrop" do
    domain "com.apple.NetworkBrowser"
    key "DisableAirDrop"
    value true
    type "bool"
  end

The resource above is available in my mac_os_x cookbook.

Changing Class Names

If you change a class name in your library, do a major version change! You don’t know who is using your library, even the undocumented parts.

Background

Recently, we had a post on the Chef mailing list that using bluepill for the Chef Server daemon process(es) was broken. Upon further investigation of the output from Chef debug output, it appeared to be an issue with Bluepill itself, but after looking into that ticket, the daemons gem had made a change.

Out of curiosity, I was compelled to find out what the source of the change in the daemons gem was. This lead to a yak shave, as first, I had to look up the daemons gem on RubyGems.org to try and find the source. The author of the gem still uses RubyForge rather than GitHub. That’s fine, but it means I have to do some link-spelunking to find where the source code lives.

Now I take a look at the change log:

Release 1.1.8: February 7, 2012
rename to daemonization.rb to daemonize.rb (and Daemonization to Daemonize) to ensure compatibility.

Release 1.1.7: February 6, 2012
start_proc: Write out the PID file in the newly created proc to avoid race conditions.
daemonize.rb: remove to simplify licensing (replaced by daemonization.rb).

Release 1.1.6: January 18, 2012
Add the :app_name option for the "call" daemonization mode.

Release 1.1.5: December 19, 2011
Catch the case where the pidfile is empty but not deleted and restart
the app (thanks to Rich Healey)

I then went to the ticket tracker to find out what the source of the changes might be. Fortunately, there was an open issue that I could reference.

My question (which I posted to the ticket) is why wouldn’t renaming a class cause the author to do a new major version? This way other Gems that rely on this as a dependency could use the paranoia operator, ~> so the broken class name wouldn’t break usage elsewhere.

I’m glad that the daemons gem author did the right thing and yanked the broken version. Open source worked well here. The process of finding this was a bit slower than it should have been, and I think that the bluepill maintainers moved too quickly to “resolve” the issue, rather than post their concern about the class naming. Kudos to thuehlinger for fixing his gem, though.

Testing With Fission

In this post, I’m going to talk about the fission gem:

I primarily use this to manage the VMware Fusion virtual machines I use for testing Opscode’s Chef Cookbooks.

This post is rather light on specific details about things that are either “common” knowledge or documented elsewhere. Particularly, I won’t tell you how to set up the virtual machines, other than a few notes that I think make it easier to manage virtual machines in this way. In other words, you’re smart and can figure them out.

Install and Configure

The first step to use the fission RubyGem is to install it. If you don’t like RubyGems, then create a package or grab the source from the GitHub link, above.

gem install fission

Test that it is detecting your VMware Fusion VMs:

fission status

Fission has a configuration file, ~/.fissionrc, which is yaml format. If the status command fails, you may need to configure fission to find the vmrun command. Here’s the example from my system:

---
vmrun_bin: /Applications/VMware Fusion.app/Contents/Library/vmrun

Install an OS on a VM

If you’re reading this, I presume you know how to install an OS in a VMware virtual machine. I do a number of tasks during the installation to make it easy and consistent to work with all my test VMs.

  • Use bridged networking with DHCP

This usually results in the least amount of hassle for connecting to the VM without any tunneling or port forwarding tomfoolery.

  • Give it a simple VM name with alphanumeric characters only.
  • Use the same hostname during installation as the VM name

In the examples below, I use my “guineapig” system. I also have other systems like “ubuntu1110”, “freebsd82” and “centos6”. This is the name you’ll use to refer to the VM with fission, so it should be short, easy to type and clearly identifiable.

  • Set the root password, even if the OS doesn’t use the root account
  • Make sure SSH as root is enabled

Some Linux distributions such as Ubuntu do not enable the root login. This is for testing, so I really don’t care, and I can always write a Chef recipe to lock things down (as I would in production) if required.

I also set a simple password that I can use with -P to knife bootstrap without the shell doing anything with special characters.

  • Use NTP

Install the NTP package for your operating system. The workflow here (and the whole point really) is to make heavy use of VMware Fusion snapshots and rollback, so it is important that the system time is correct. I customized the bootstrap templates I use to add ntpdate.

Using Fission

And now the moment you’ve been waiting for. First, see the fission README for full detail on the commands available. I’m going to focus here on how I use it.

After the install and post install tasks are done, I create a new snapshot for the VM.

% fission status
guineapig        [running]
% fission snapshot create guineapig base

The name is “base” because thats a good name for a baseline. It can be useful to create specifically named snapshots for particular purposes.

I use Opscode Hosted Chef as my server and I already have my local workstation set up with the validation key, a Chef repository and have uploaded the cookbook(s) I use for testing. I’ll use “knife bootstrap” to kick off a run on my VM:

% knife bootstrap 10.1.1.129 -x root -Pvanilla -r 'recipe[apache2]'
...
INFO: service[apache2] restarted
INFO: Chef Run complete in 44.324473 seconds

Sweet, it worked. However, if the Chef run fails, I can log in as root, fix the bug and rerun, or whatever else may need to be done. Then once I’m ready to reset the VM, fission comes back to play.

% fission snapshot revert guineapig base
Reverting to snapshot 'base'
Reverted to snapshot 'base'

Note that fission will poweroff the VM when reverting the snapshot. Turn it on again with the start command.

% fission status
guineapig        [not running]

% fission start guineapig
Starting 'guineapig'
VM 'guineapig' started

% fission status
guineapig        [running]

And after logging in, we can see that apache2 is not installed as it should not be after the snapshot is restored.

% ssh root@guineapig
root@guineapig's password:
root@guineapig:~# dpkg -l apache2
No packages found matching apache2.

The VM is now ready to do my bidding once again.

Cleanup

Note that reverting the snapshot doesn’t delete the Chef node or client objects. Since fission is a Ruby library, a simple knife plugin can wrap up all the fission revert, restart and Chef cleanup, though. I called mine nukular.

% knife nukular guineapig base guineapig.int.example.com

And here’s the plugin I’m using:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
require 'chef/knife'

  module KnifePlugins
    class Nukular < Chef::Knife
      deps do
        require 'fission'
        require 'chef/node'
        require 'chef/api_client'
      end

      banner "knife nukular VM SNAPSHOT [NODE]"

      def run
        vm, snapshot = @name_args
        node = @name_args[2].nil? ? vm : @name_args[2]
        Fission::Command::SnapshotRevert.new(args=[vm, snapshot]).execute
        Fission::Command::Start.new(args=[vm]).execute
        Chef::Node.load(node).destroy
        Chef::ApiClient.load(node).destroy
      end
    end
  end

The command-line usage takes 2 or 3 arguments. The first two must be the VM name and the snapshot name, e.g. guineapig and base. If the node name is different than the VM name, then specify it.

% knife nukular guineapig base guineapig.int.example.com

Note that the plugin has zero error handling or any other sensible things. You may want to modify it before you use it. Or not, these are just test systems after all.

Full example

Minus the output from the commands, here is the full output of testing the Opscode apache2 cookbook on my guinea pig. Assume that all the required things from my Chef Repository have been uploaded to the Chef Server and the knife configuration is correct. Also, the chef-full bootstrap template specified here is a customized version of the template in the Chef source master branch template that has ntpdate -u pool.ntp.org in it.

1
2
3
4
5
% fission status
% fission snapshot create guineapig testing-apache2
% knife bootstrap 10.1.1.129 -x root -P vanilla \
  -r 'recipe[apache2]' -d chef-full
% knife nukular guineapig testing-apache2 guineapig.int.housepub.org

That’s it. I hope you find this helpful!

DNSimple Self Registration Recipe

Earlier this month, I completed a switch to DNSimple for my domain’s DNS provider. I am still happy with the switch, and finally, just now, got around to writing a recipe to have my systems automatically register themselves in DNS.

In the post, I described automatically adding the DNS entries with the dnsimple cookbook. I did this as a proof of concept, but I didn’t add it to all my nodes, instead using my existing data bag-driven solution.

That said, this post serves as a brief document on how you can mimic this behavior with your own environment.

Encrypted Data Bag

I put my DNSimple credentials in an encrypted data bag. Since I have to decrypt and read the entire thing anyway, I also store the relevant data there. I keep my encrypted data bags in a bag called secrets. The structure looks like this:

1
2
3
4
5
6
7
{
  "id": "dnsimple",
  "api_token": "DNSimple API Token Here",
  "domain": "your-domain.example.com",
  "username": "DNSimple username",
  "password": "DNSimple password"
}

Replace the values with your values. Encrypting the data is optional, but requires that you create a secret key or key file. Read my previous post on the topic for more information.

1
2
% knife data bag from file secrets dnsimple.json
% knife data bag from file secrets dnsimple.json --secret-file /etc/chef/encrypted_data_bag_secret

The first command just uploads the data bag item, the second encrypts it. Note that I manage my workstation with Chef, so I will use the same secret file as the Chef default. The secret file needs to be copied to each system that will need it.

Recipe

The recipe looks like this:

1
2
3
4
5
6
7
8
9
dnsimple = encrypted_data_bag_item("secrets", "dnsimple")
dnsimple_record "#{node['hostname']}.int.#{dnsimple['domain']}" do
  content node['ipaddress']
  type "A"
  action :create
  username dnsimple['username']
  password dnsimple['password']
  domain dnsimple['domain']
end

As mentioned in the previous blog post, the encrypted_data_bag_item method is in a library. Either add that library to your cookbook, or use the class directly.

1
dnsimple = Chef::EncryptedDataBagItem.load("secrets", "dnsimple")

If you’re not using an encrypted data bag, then the item can be accessed with the normal method:

1
dnsimple = data_bag_item("bagname", "dnsimple")

The real work happens in the dnsimple_record LWRP, which will add an “A” record for the system running the recipe. Note that the actual entry is going to use the int subdomain, and it will use the domain stored in the data bag item. It also will use the default IP address of the node, which means the IP for the interface with the default route.

iTerm2 With Tmux

A new “which tool is best” battle is raging in the internets amongst developers and system administrators. The contestants are screen and tmux, and the jury is still out. This is very much an argument over what color to paint the bikeshed, but with the latest version of iTerm2, I think tmux is even more compelling. Personally, I chose tmux awhile ago.

At my day job, I worked with a customer that uses tmux for remote pairing between developers. At the time, tmux had better customizability, and better split-pane support (screen didn’t yet have vertical split). I stuck with tmux ever since, and was very pleased when an iTerm2 update announced integration with tmux.

iTerm2

For those who aren’t aware, iTerm2 is an alternate terminal program for Mac OS X. It is actually an updated codebase from the original, iTerm, which is effectively unmaintained. iTerm2 offers a lot of excellent features like split panes, Growl support, and many more.

One of the excellent new features is integration with tmux.

iTerm2’s tmux integration

If you already have iTerm2 installed, you may have seen the update check prompt you to update. You also need to install a special version of tmux that has the integration patched. The iTerm2 author is working with the tmux author to get this into the latest tmux codebase, so hopefully the custom compiled version won’t be necessary soon.

Using the new feature is relatively straightforward. Start up iTerm2 like normal. Then run tmux -C to open a new iTerm2 window that works like tmux.

Launch tmux in iTerm2

Use the tmux menu in iTerm2 to open new windows in tmux. Note that there are keyboard shortcuts for each of these, and they are not the same as the tmux window commands.

Use tmux menu to open buffers

You can also attach to a tmux session running in iTerm2. In the screenshot, this is running on the same system, for example purposes. However, since OS X has SSH, this can be useful if you want to SSH to another system in the local network and connect to the running session. For example, the system shown below is my wife’s iMac over screensharing, but I wouldn’t need to use screenshare (or participate in its lag) to connect to this anymore. The same holds true for connecting to my work laptop if necessary.

Attach to tmux session in iTerm2

In this final example screenshot, you can see that I have multiple panes split in one iTerm2 tab. These correspond to the split windows in the attached tmux in the other window. Also, the two tabs in the iTerm2 window are separate tmux windows (0:zsh and 1:zsh).

iTerm2 panes are panes in tmux

And now, I can SSH to that system and attach to the tmux session started by iTerm2.

SSH to remote and run tmux attach

tmux is attached to iTerm2 session

Automating Installation with Chef

Installing OS X apps is quite easy, but I automate them with Chef anyway. While it is a simple “install update and restart”, with a couple commands to install the update, I do have three systems I want this on. I updated my iterm2 cookbook to support installing the tmux integration for iTerm2. This is disabled by default, so it needs to be enabled via a node attribute. For example, I have this in my workstation role applied to my OS X workstations.

1
2
3
4
5
6
7
8
name "workstation"
description "Mac OS X workstations"
run_list("recipe[tmux]")
default_attributes(
  "iterm2" => {
    "tmux_enabled" => true
  }
)

Check out the iterm2 cookbook’s README for more information.

Chef Report Handler for Growl

A few weeks ago, I listened to the Changelog Podcast episode featuring Chris Forsythe, lead of the Growl project. I actually don’tWdidn’t use Growl for a long time, because I really disliked notifications of any kind, as they are distracting. However, I do appreciate the project, and supporting them by purchasing Growl on the App Store seemed totally reasonable.

Of course, buying the app means it was installed on the spot. I always resisted it in the past because it was bundled with so many apps, but I don’t want unsolicited software to show up on my computer. Now, it seemed a bit more natural, and I made a few configuration tweaks (I should put those into Chef…). I’m actually happy to use it now, especially since it has a nice network accessible API.

Growlnotify

Shortly after I started using Growl on my work system, I looked into ways I could get notifications fired off from the command-line after long running processes finished. In particular, I wanted to get a notification that knife ec2 server create was done. I found the growlnotify program, which is available via homebrew. This is quite a nifty tool, and I set it to use immediately, doing things like this:

1
2
3
4
5
knife ec2 server create \
  -f m1.small -I $lucid_small -x ubuntu \
  -r 'role[base],role[webserver]' && \
growlnotify -m "Finished launching 1 instance" || \
growlnotify -m "failed to launch instance"

I could kick off the server creation, switch focus to another workspace, and then know via growl if the instance was created.

Chef Handler

As many know, I manage my workstations with Chef. Chef has a pretty cool exception and report handler API that has a lot of flexibility. I thought it would be fun to throw together a simple report handler that would send a growl notification after a Chef run. In this case, it will report the elapsed time of the run if it was successful, or report an exception if it failed.

Using the handler is pretty straightforward. Install the chef-handler-growl Gem, then configure chef-client (or solo).

1
2
3
require "chef/handler/growl"
report_handlers << Chef::Handler::Growl.new
exception_handlers << Chef::Handler::Growl.new

Then run Chef, and see something like this:

Chef Handler Growl

The handler is available as a RubyGem. You can also view the source. I created issues on the GitHub project for the two items on the roadmap, too.

Switching to DNSimple

Reminder: this blog reflects my opinions and thoughts, and not those of my employer, Chef Software, Inc.

Like any good sysadmin, I have my own domain for email and other purposes. I actually have had a couple, but this post is about my current one. I originally set it up through Google Apps a couple years ago, including registering the new domain with Google Apps’ default registrar, GoDaddy. For the most part, it was pretty simple and painless to set up, including private registration via Domains by Proxy. Yay!

However, as I automated more components of my home network with Chef, I found the lack of API driven DNS rather frustrating. At last count, I had 15 distinct networked devices, counting all the computers, consoles, mobile devices, etc. This does not count the virtual machines that I manage as a part of my daily job in doing Chef cookbook development and testing, which should all have their own DNS entries, since I’ll access them over the network, and remembering IPs is ridiculous.

Internal DNS Server

I use DJB’s tinydns as my DNS server, and it is automated with the Opscode Chef Cookbook. The first incarnation of this setup was a single monolithic template file containing all the entries in my local network zone, delivered by the djbdns::tinydns-internal recipe, like so:

1
2
3
4
5
template "#{node[:djbdns][:tinydns_internal_dir]}/root/data" do
  source "tinydns-internal-data.erb"
  mode 0644
  notifies :run, resources("execute[build-tinydns-internal-data]")
end

This was great, and simple to manage for this single purpose setup. At some point, I wrote cookbooks for unbound and powerdns, as I was evaluating whether one or the other might be easier to modularize. In the process, I created a data bag of all my DNS entries that I could step through in templates, so I could use the same data without caring which software was going to consume it. In the end, I extended the djbdns cookbook with a lightweight resource and provider, and added usage to the djbdns::tinydns-internal recipe like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
dns = data_bag_item("djbdns", node[:djbdns][:domain].gsub(/\./, "_"))
#
file "#{node[:djbdns][:tinydns_internal_dir]}/root/data" do
  action :create
end
#
%w{ ns host alias }.each do |type|
  dns[type].each do |record|
    record.each do |fqdn,ip|
      #
      djbdns_rr "#{fqdn}.#{dns['domain']}" do
        cwd "#{node[:djbdns][:tinydns_internal_dir]}/root"
        ip ip
        type type
        action :add
        notifies :run, "execute[build-tinydns-internal-data]"
      end
      #
    end
  end
end

The data bag itself looks something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
  "id": "int_example_com",
  "domain": "example.com",
  "ns": [
    { "ns: "127.0.0.1" }
  ],
  "alias": [
    { "gw":         "10.10.20.1" },
    { "smb":        "10.10.20.20" },
    { "files":      "10.10.20.20" },
    { "apt":        "10.10.20.120" },
    { "yum":        "10.10.20.120" }
  ],
  "host": [
    { "tavern":          "10.10.20.1" },
    { "cask":            "10.10.20.20" },
    { "cider":           "10.10.20.101" },
    { "merlot":          "10.10.20.103" },
    { "bourbon":         "10.10.20.104" },
    { "iphone":          "10.10.20.105" },
    { "doppelbock":      "10.10.20.106" },
    { "ipad":            "10.10.20.107" },
    { "wii":             "10.10.20.108" },
    { "xbox":            "10.10.20.109" },
    { "htpc":            "10.10.20.110" },
    { "virt1test":       "10.10.20.120" }
  ]
}

I am pretty pleased with this approach in that this data can be used no matter what DNS resolver software I choose. While this is great for the more static part of my network, as I mentioned I do have some dynamic usage where I create new virtual machines, and I really want them to register themselves in DNS automatically.

Enter DNSimple

A few months ago I decided to switch over to DNSimple. The service was compelling over the alternatives for a few reasons:

  • Low cost ($3/mo) for my size of account.
  • Very simple interface
  • API for managing records (!)
  • Reputation for great service

Darrin Eden wrote a cookbook for automatically creating records through the API, too, so half my work for automating with Chef was already done!

However, for various reasons I procrastinated the switchover. After all, my existing solution worked ok for my purposes. Then after seeing GoDaddy show up on the SOPA supporters list, and being one a contributing author to the legislation(*), I decided that was the last straw and I busted a move to finish the switch.

Honestly, from the DNSimple side, it couldn’t have been a better experience. They have one-click services for managing DNS records for a variety of common services – including Google Apps! It took some time and hassle to move my domain out of GoDaddy, since their interface is rather clunky, and I had to unprotect things through Domains by Proxy to make the move, but after a couple hours everything was fine. DNSimple has some tips for migrating, no matter who your current registrar is.

Now for the truly fun part!

Automated DNS with Chef

Using the dnsimple cookbook is very straightforward. You create an “A” record like this:

1
2
3
4
5
6
7
8
9
10
dnsimple_record "cask.example.com" do
  name "cask"
  domain "example.com"
  content "10.10.20.20"
  type "A"
  action :create
  username node[:dnsimple][:username]
  password node[:dnsimple][:password]
  domain node[:dnsimple][:domain]
end

Yes, that is a private network IP, and yes it is going to be registered in public DNS. It really doesn’t matter that much in my (and others’) opinion. Especially given that zomg, I just created a DNS entry with Chef!

By default, the cookbook does assume, and use, node attributes for storing the username and password. This can be set by a role, but it means that all nodes will have the data. For my use, I decided to put these values in a data bag, and because they are sensitive, I used an encrypted data bag. I also wanted to reuse the data bag from my earlier DNS example, so I wrote a recipe like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
dnsimple = encrypted_data_bag_item("secrets", "dnsimple")
dns = data_bag_item("dns", "int_example_com")
#
%w{ host alias }.each do |type|
  dns[type].each do |record|
    record_type = type =~ /^host$/ ? "A" : "CNAME"
    record.each do |hostname,ip|
      #
      dnsimple_record "#{hostname}.#{dns['domain']}" do
        name hostname
        content ip
        type record_type
        action :create
        username dnsimple['username']
        password dnsimple['password']
        domain dnsimple['domain']
      end
      #
    end
  end
#
end

I put that recipe in my “dnsserver” role, ran Chef, and boom, all my DNS entries are updated on systems I don’t have to manage, and all around the world.

% host cask.int.example.com 8.8.8.8
Using domain server:
Name: 8.8.8.8
Address: 8.8.8.8#53
Aliases:

cask.int.example.com has address 10.10.20.20

What a wonderful redundant distributed key value store :–).

Note that the encrypted_data_bag_item method used in the recipe is in a cookbook library. I wrote about that in an earlier blog post. It is pretty simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Chef
  class Recipe
    #
    def encrypted_data_bag_item(bag, item, secret_file =
        Chef::EncryptedDataBagItem::DEFAULT_SECRET_FILE)
      DataBag.validate_name!(bag.to_s)
      DataBagItem.validate_id!(item)
      secret = EncryptedDataBagItem.load_secret(secret_file)
      EncryptedDataBagItem.load(bag, item, secret)
    rescue Exception
      Log.error("Failed to load data bag item: #{bag.inspect} #{item.inspect}")
      raise
    end
    #
  end
end

Drawbacks

While conveniently reusing the data I already had for my DNS entries, the dnsimple_record provider does take about a second on my internet connection for each entry to check if it’s there. This makes the DNS server’s Chef client run take over a minute, where it used to be less than 12 seconds. Many of the entries that are in the DNS data bag are on systems that can add their own records, and will soon once I refactor things a bit.

Other Uses

A number of the entries in the DNS data bag are CNAMEs for services that run on my home LAN server. I have internal services like Netatalk/Time Machine, Samba, or Munin. I also have external services like OpenVPN, SSH and Teamspeak. I’ll add DNSimple records for each of these so the recipes can automatically register new DNS entries, eliminating a step in bringing up a new service.

Open Source is Awesome

Chef is open source, of course, as is the dnsimple cookbook that I’m using. While working with the cookbook as describe above over the last couple days, I made some improvements and I sent a pull request, which has been merged and released. Thanks Darrin!

If you use Chef and are looking for an API driven way to manage DNS entries for your systems, I strongly recommend DNSimple as a provider, and the dnsimple cookbook to tie it all together.

(*) This isn’t a political-blog-soap-box, but this really was the final motivator.

Ruby in Ubuntu 11.10

I was playing around with Ubuntu 11.10 the other day, to explore some of the changes that have happened to Ruby lately, and thought I’d share my findings.

First off, there are still Ruby 1.8(.7p352) packages. This is the default you get with the ruby package. However, the ruby1.9.1 has Ruby 1.9.2p290 as the default version. This isn’t the absolute latest released (officially, 1.9.3 is the latest stable release), but it’s a widespread release that a lot of people are using in production.

The main package for getting the Ruby executables is ruby1.9.1. It is now a “full” installation and includes:

  • /usr/bin/gem1.9.1
  • /usr/bin/rdoc1.9.1
  • /usr/bin/rake1.9.1
  • /usr/bin/erb1.9.1
  • /usr/bin/ri1.9.1
  • /usr/bin/ruby1.9.1
  • /usr/bin/irb1.9.1
  • /usr/bin/testrb1.9.1

Previous versions had these as separate packages, and you had to remember to install everything to have a full Ruby installation available.

In the package name and the above binaries, 1.9.1 indicates the Ruby library compatibility, 1.9.2 is compatible with 1.9.1.

The Debian alternatives system is used to set the default ruby, gem and irb binaries in the $PATH as links to the 1.9.1 versions, e.g.:

1
2
3
% ls -l /usr/bin/ruby /etc/alternatives/ruby
lrwxrwxrwx 1 root root 18 2011-11-24 00:02 /etc/alternatives/ruby -> /usr/bin/ruby1.9.1
lrwxrwxrwx 1 root root 22 2011-11-24 00:02 /usr/bin/ruby -> /etc/alternatives/ruby

Users can also update RubyGems via the gem update --system command, but a shell environment variable must be exported first.

1
2
export REALLY_GEM_UPDATE_SYSTEM=yes
sudo -E gem update --system

The -E is significant so the variable is available via sudo. Without this variable set, this command will warn noisily that you should know what you’re doing, and what this is up to. Personally, I’m fine with RubyGems 1.3.7, as that is the version that the Chef gem_package provider uses for the API. With the default RubyGems, you also get binaries in /usr/local/bin, rather than the awkward /var/lib/ruby location in earlier versions.

These changes are a result of work that has been going in Debian Wheezy by the Ruby packaging team, kicked off by Lucas Nussbaum.

That said, let’s put this to use so we can have Chef running under the Ruby 1.9.2 available via APT.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
% sudo apt-get install ruby1.9.1 ruby1.9.1-dev build-essential
Reading package lists... Done
Building dependency tree
Reading state information... Done
build-essential is already the newest version.
Suggested packages:
  ruby1.9.1-examples ri1.9.1 graphviz
The following NEW packages will be installed:
  libruby1.9.1 ruby1.9.1 ruby1.9.1-dev
0 upgraded, 3 newly installed, 0 to remove and 1 not upgraded.
Need to get 0 B/5,027 kB of archives.
After this operation, 19.5 MB of additional disk space will be used.
Selecting previously deselected package libruby1.9.1.
(Reading database ... 141637 files and directories currently installed.)
Unpacking libruby1.9.1 (from .../libruby1.9.1_1.9.2.290-2_amd64.deb) ...
Selecting previously deselected package ruby1.9.1.
Unpacking ruby1.9.1 (from .../ruby1.9.1_1.9.2.290-2_amd64.deb) ...
Selecting previously deselected package ruby1.9.1-dev.
Unpacking ruby1.9.1-dev (from .../ruby1.9.1-dev_1.9.2.290-2_amd64.deb) ...
Processing triggers for man-db ...
Setting up libruby1.9.1 (1.9.2.290-2) ...
Setting up ruby1.9.1 (1.9.2.290-2) ...
update-alternatives: using /usr/bin/gem1.9.1 to provide /usr/bin/gem (gem) in auto mode.
update-alternatives: using /usr/bin/ruby1.9.1 to provide /usr/bin/ruby (ruby) in auto mode.
Setting up ruby1.9.1-dev (1.9.2.290-2) ...
Processing triggers for libc-bin ...
ldconfig deferred processing now taking place

One of Chef’s dependency gems is json, which has native C extensions, so the development headers for Ruby, and build tools are required (though already installed on my system).

1
2
3
4
% ruby --version
ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-linux]
% gem --version
1.3.7

Now, I can install Chef as a RubyGem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
% sudo gem install chef
Building native extensions.  This could take a while...
[Version 0.7.8] test suite cleanup (eliminated some race conditions related to queue.message_count)
Building native extensions.  This could take a while...
Successfully installed mixlib-config-1.1.2
Successfully installed mixlib-cli-1.2.2
Successfully installed mixlib-log-1.3.0
Successfully installed mixlib-authentication-1.1.4
Successfully installed yajl-ruby-1.1.0
Successfully installed systemu-2.2.0
Successfully installed ohai-0.6.10
Successfully installed mime-types-1.17.2
Successfully installed rest-client-1.6.7
Successfully installed bunny-0.7.8
Successfully installed json-1.5.2
Successfully installed polyglot-0.3.3
Successfully installed treetop-1.4.10
Successfully installed net-ssh-2.1.4
Successfully installed net-ssh-gateway-1.1.0
Successfully installed net-ssh-multi-1.1
Successfully installed erubis-2.7.0
Successfully installed moneta-0.6.0
Successfully installed highline-1.6.8
Successfully installed uuidtools-2.1.2
Successfully installed chef-0.10.4
21 gems installed

And running Chef just works:

1
2
3
4
% sudo chef-client
[Thu, 24 Nov 2011 14:02:38 -0700] INFO: *** Chef 0.10.4 ***
...
[Thu, 24 Nov 2011 14:02:50 -0700] INFO: Chef Run complete in 9.862972181 seconds

The results of the node object on the Chef Server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
% knife node show virt1test -a languages.ruby
languages.ruby:
  bin_dir:        /usr/bin
  gem_bin:        /usr/bin/gem1.9.1
  gems_dir:       /var/lib/gems/1.9.1
  host:           x86_64-pc-linux-gnu
  host_cpu:       x86_64
  host_os:        linux-gnu
  host_vendor:    pc
  platform:       x86_64-linux
  release_date:   2011-07-09
  ruby_bin:       /usr/bin/ruby1.9.1
  target:         x86_64-pc-linux-gnu
  target_cpu:     x86_64
  target_os:      linux
  target_vendor:  pc
  version:        1.9.2

Overall, I’m happy with the changes that the Debian Ruby Packaging Team has made, and I’m glad to see these come to Ubuntu in 11.10+. It’s not the absolute latest version available, but this is good forward progress for binary Linux distributions. I also like that the alternatives system is used, so users could choose to install and use other Ruby interpreters. Hopefully these changes quell some of the arguments about Debian and Ubuntu and their handling of Ruby.

Chef on Windows HTPC

Over the past 20 years, I always had a Windows (or DOS!) PC as the main system I use at home. The primary purpose was for gaming, although building my own systems is also a hobby. In 2007, I wanted to build a new system to use as a home theater PC. Originally, I built it with Windows Vista – Windows Media Center was Vista’s killer app! I painstakingly installed software, tweaked system settings and tuned the registry. Then Windows 7 came along with further improvements. After almost 20 years of reinstalling from scratch for new versions of Windows, I treated this upgrade as no different.

Unfortunately, I didn’t capture all the system tweaks and settings. I didn’t get all the software reinstalled. I did what I remembered, and W7 did have some improvements that made extra software and tweaks unnecessary. The thing I was lacking in the rebuild, and up until recently, was configuration management for Windows.

Enter Chef

Recently, my employer Opscode announced broader Windows support for Chef. The awesome Chef community, and our development team have added some key features to Chef in the form of core libraries and cookbooks. While I did get Chef running on my HTPC a few months ago, I had only used it to test out the powershell cookbook, and also wrote a silverlight cookbook as a proof of concept. In the mean time, the windows cookbook cookbook received some serious leveling up thanks to Seth Chisamore.

Windows Cookbook

The Windows cookbook has a number of excellent “primitives” in the form of libraries and resources. You can read all about that on The Chef Wiki. For now, I’ll talk about how I captured some of the configuration so I don’t have to remember next time (Windows 8, coming soon…). Of interest here are the windows_registry and windows_auto_run resources.

My HTPC’s cookbooks

I wrote two cookbooks this weekend. First, is a general “Windows Media Center” cookbook called, aptly, “wmc”. This will capture the specific registry and other configuration related to Windows Media Center itself. The idea with this one is it manages only Windows Media Center. It’s not ready for release, but I’ll post some snippets of code here as I go.

The second cookbook I created is specifically for the Home Theater PC itself, named htpc. This is going to contain all my preferences and biases of how I want my HTPC to look like in order to provide entertainment in my house.

Windows Media Center (wmc)

First and foremost, I want to make sure that Windows Media Center is running at boot time, and the windows_auto_run resource fits the bill. This resource modifies the registry to update the key HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run, using the windows_registry resource. It does operate at the machine level, not just the current user, but I only have one user on my HTPC, so it’s okay. Here’s the resource:

1
2
3
4
5
windows_auto_run "Windows Media Center" do
  program "RunDLL32.exe C:/Windows/ehome/ehuihlp.exe,BootMediaCenter"
  not_if { Registry.value_exists?(AUTO_RUN_KEY, 'Windows Media Center') }
  action :create
end

Next, I want to tweak a few other registry settings. Some of these I discovered through an MSDN blog post, others are from setting up WMC itself and observing the “HKCU” registry. Not the most sophisticated way, but I don’t see a great “diff” tool for the registry. Because the name of the HKCU and HKLM keys for WMC are long, I set them to a couple local variables, via cookbooks/attributes/default.rb.

1
2
default['wmc']['reg_hkcu'] = 'HKCU\Software\Microsoft\Windows\CurrentVersion\Media Center'
default['wmc']['reg_hklm'] = 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Media Center'

Then in the recipe, I simply assign local variables.

1
2
hkcu = node['wmc']['reg_hkcu']
hklm = node['wmc']['reg_hklm']

Now, for the registry changes. I use a 30 second skip forward and back with my remote during recorded TV shows, so I can avoid commercials.

1
2
3
4
5
6
windows_registry "#{hkcu}\\Settings\\VideoSettings" do
  values(
    'SkipAheadInterval' => 0x7148,
    'InstantReplayInterval' => 0x7148
  )
end

I also optimize the display for LCD/Plasma (I have a RP DLP, but I digress).

1
2
3
windows_registry hkcu do
  values 'TVSkin' => 0
end

For now, that’s all the wmc cookbook has going for it. I do have plans to enable additional features of WMC, install useful plugins, and improve playback quality and performance. Mainly, I need to find my old notes :–).

Home Theater PC (htpc)

The htpc cookbook will contain additional tweaks and settings for how I want my HTPC configured. I don’t know if I’ll release it publicly, as it is pretty specific to me, but it might be useful as examples for others, so stay tuned to my GitHub account for updates.

First up are some local performance tweaks. These are done to Explorer(.exe), and I don’t remember the source.

1
2
3
4
5
6
7
windows_registry "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer" do
  values(
    'NoLowDiskSpaceChecks' => 1,
    'NoResolveSearch' => 1,
    'NoResolveTrack' => 1
  )
end

Second, since this system is where all my music files live, I have iTunes installed for Home Sharing. It doesn’t do much good if iTunes isn’t running, however, so I use windows_auto_run to start it up.

1
2
3
4
5
windows_auto_run "iTunes" do
  program "C:/Program\ Files/iTunes/iTunes.exe"
  not_if { Registry.value_exists?(AUTO_RUN_KEY, "iTunes") }
  action :create
end

It’s pretty simple so far, but I have some plans in store for my HTPC cookbook.

HTPC Cookbook Plans

I do plan to add some new features to the htpc cookbook. First, I want to make sure all the software I need is installed. While the system mainly sits in WMC (with iTunes in the background), I do have some additional software and utilities that I like to have for managing my media library.

  • iTunes – I already have the software installed, but it would be nice to not have to do that during system setup (Windows 8, I say…).
  • Google Chrome – there’s no abc.com, nbc.com, etc plugins for WMC (yet?), so I need a browser to go to the respective web sites to catch up on older episodes.
  • CDBurnerXP – decent CD/DVD burning software, rarely used but useful for backups.
  • Logitech Harmony Remote Software – I have a Harmony 700 universal remote (and it’s awesome!). This one might be tricky, but really I just want to make sure the software is installed. Configuration profiles are stored in an online support account with Logitech as far as I’m aware.
  • Netflix plugin for WMC – This will probably go into the wmc cookbook, though.
  • Additional performance tweaks – There are a whole slew of tweaks that I know I made, I just don’t remember them all. These will be added with windows_registry resources.
  • Manage services – Everyone who has a gaming PC knows about Black Viper’s Windows Service Guide(s).
  • Windows Features – With the windows_feature resource, Chef can manage core features included in Windows installations.
  • Logging, monitoring – What kind of system administrator would I be if I didn’t also monitor my system for performance? :–)

Some of the things I want to manage on my HTPC are covered by other cookbooks I have, or exist on the Chef Community Site.

  • Steam – This system is still capable of running some games, like StarCraft II, TeamFortress 2 and Left 4 Dead (1 & 2). I have a Steam cookbook that works on OS X, it just isn’t released (yet).
  • Teamspeak 3 – I already have a teamspeak3 cookbook, I’ll just need to add installation of the client for Windows there.
  • A text editor – Since I have games, and games have configuration files, it makes sense to … wait, I should put those in Chef too! Or at least, in a Version Control System and check out the repository…
  • Git – For the above. This will likely be a contribution to Opscode’s git cookbook. Though I’ll still install a text editor of some kind.

These changes, updates and releases will be announced on this blog, mainly through my twitter and Google+ accounts. Stay tuned!