Arduino-Powered Webcam Mount

Earlier this month, I completed yet another journey around the biggest star in our galaxy. Some of my beloved family members thought this would be a good occasion to send me some cash, and I also got a gift card for being plain awesome at work. Even though we really do need a bigger car and whatnot, my wife insisted that I only spend this money on myself and whatever I wanted.

Little did she know the can of worms she just opened up.

I took pretty much all of the money and blew it on stuff for my electronics projects. Up to this point, my projects have all been pretty boring simply because nothing ever moved--it was mostly just lights turning on and off or changing colors. Sure, that's fun, but things really start to get interesting when you actually interact with the physical world. With the birthday money, I was finally able to buy a bunch of servos to begin living out my childhood dream of building robots.

My first project since getting all of my new toys was a motorized webcam mount. My parents bought me a Logitech C910 for my birthday because they were tired of trying to see their grandchildren with the crappy webcam that is built into my laptop. It was a perfect opportunity to use SparkFun's tutorial for some facial tracking (thanks to OpenCV) using their Pan/Tilt Servo Bracket.

It took a little while to get everything setup properly, but SparkFun's tutorial explains perfectly how you can get everything setup if you want to repeat this project.

The problem I had with the SparkFun tutorial, though, is that it basically only gives you a standalone program that does the facial tracking and displays your webcam feed. What good is that? I actually wanted to use this rig to chat with people!! That's when I set out to figure out how to do this.

While the Processing sketch ran absolutely perfect on Windows, it didn't want to work on my Arch Linux system due to some missing dependencies that I didn't know how/care to satisfy. As such, I opted to rewrite the sketch using Python so I could do the facial tracking in Linux.

This is still a work in progress, but here's the current facial tracking program which tells the Arduino where the webcam should be pointing, along with the Arduino sketch.

Now that I could track a face and move my webcam in Linux, I still faced the same problem as before: how can I use my face-tracking, webcam-moving program during a chat with my mom? I had no idea how to accomplish this. I figured I would have to either intercept the webcam feed as it was going to Skype or the Google Talk Plugin, or I'd have to somehow consume the webcam feed and proxy it back out as a V4L2 device that the Google Talk Plugin could then use.

Trying to come up with a way of doing that seemed rather impossible (at least in straight Python), but I eventually stumbled upon a couple little gems.

So the GStreamer tutorial walks you step-by-step through different ways of using a gst-launch utility, and I found this information very useful. I learned that you can use tee to split a webcam feed and do two different things with it. I wondered if it would be possible to split one webcam feed and send it to two other V4L2 devices.

Enter v4l2loopback.

I was able to install this module from Arch's AUR, and using it was super easy (you should be root for this):

modprobe v4l2loopback devices=2

This created two new /dev/video* devices on my system, which happened to be /dev/video4 and /dev/video5 (yeah... been playing with a lot of webcams and whatnot). One device, video4, is for consumption by my face-tracking program. The other, video5, is for VLC, Skype, Google+ Hangouts, etc. After creating those devices, I simply ran the following command as a regular user:

gst-launch-0.10 v4l2src device=/dev/video1 ! \
    'video/x-raw-yuv,width=640,height=480,framerate=30/1' ! \
    tee name=t_vid ! queue ! \
    v4l2sink sync=false device=/dev/video4 t_vid. ! \
    queue ! videorate ! 'video/x-raw-yuv,framerate=30/1' ! \
    v4l2sink device=/dev/video5

There's a whole lot of stuff going on in that command that I honestly do not understand. All I know is that it made it so both my face-tracking Python program AND VLC can consume the same video feed via two different V4L2 devices! A co-worker of mine agreed to have a quick Google+ Hangout with me to test this setup under "real" circumstances (thx man). It worked :D Objective reached!

I had really hoped to find a way to handle this stuff inside Python, but I have to admit that this is a pretty slick setup. A lot of things are still hardcoded, but I do plan on making things a little more generic soon enough.

So here's my little rig (why yes, I did mount it on top of an old Kool-Aid powder thingy lid):

And a video of it in action. Please excuse the subject of the webcam video, I'm not sure where that guy came from or why he's playing with my webcam.

Lots Of Happenings

Yay! First post in nearly 4 months! I feel kinda bad for leaving my April Fools Day joke on the front page for such a long time, but lucky for me my blog isn't very popular! I could have found myself in a world of hurt.

I'd like to give you all a quick update on what's been going on in my life that somewhat justifies a 4-month window of no blog posts. First of all, we had 25% of our backend development team (1 person, leaving 3 developers) get fired earlier in the year, so the workload at my day job got to be a bit heavier.

Second, my wife and I were pregnant with identical twin girls. It was a relatively high-risk pregnancy, so we spent many days in the hospital for checkups and whatnot. The doctors gave her somewhat strict bed rest orders, and I worked from home since the beginning of July (my job is awesome that way) so I could keep and eye on my wife and help with our soon-to-be two year old son.

Third, we had our identical twin girls this past Tuesday. They arrived at 9:25 and 9:27, and their names are Claire and Jane. Claire weighed 5 lb 3 oz, and Jane weighed 4 lb 14 oz. As expected, they lost a bit of weight at the beginning, but they're starting to gain weight again. My wife and the girls are all doing very well.

My son, on the other hand, is starting to realize that his world is changing quite drastically. We're trying to give him as much attention as we can, but it's definitely not the amount that he's used to. He seems to do very well with his new baby sisters, but there have already been several episodes where he just breaks down. It's sad.

I have a few days of leave and vacation that I'll be taking to help get everything settle at home. The girls are still in the "let's sleep all day and night until we're hungry" sort of phase (which is quite awesome), and my wife is up most of the night with them. That means the only person who doesn't sleep a ton during the day is my son, so we get some good father/son time.

Anyway, on to the nerdy stuff. Since I find myself with a couple hours of downtime here and there, I plan to do a bug-smashing ticket-resolving spree. Just as my blog has sat dormant for months, so have many of my side projects. Now is the time to change that!

PIR Motion Sensor + LCD Screen + Arduino Uno

For one reason or another, I've recently had the urge to follow in my father's footsteps and start tinkering with electronics. He's basically a wizard. He has fixed a lot of appliances that others tossed out the door without the slightest bit of investigation. I think he did this with the monitor I had hooked up to my very first computer. He just popped open the case, found the problem, soldered some solution in there, and that monitor worked for probably a decade afterwards. Amazing stuff.

Anyway, I have an itch to create a laser trip wire (don't ask). I took a basic electronics class my freshman year in college, so I am already familiar with resistors, capacitors, ICs, breadboards, soldering, etc. It doesn't seem like a very daunting task to create that trip wire, but I'm starting fresh with electronics (it's been a good 8 or 9 years since I last soldered or anything like that).

One of my co-workers mentioned the Arduino as something to get me started on the trip wire, so I started doing some research here and there. The more I read, the more excited I got. I wanted soo badly to buy all of the junk that I'd need to start tinkering with an Arduino, but I didn't think my wife would appreciate that--especially around Christmas time.

Several of my awesome relatives sent me very generous gift cards for Christmas this year, which finally gave me the opportunity to buy a load of stuff for the Arduino without feeling bad. So I ordered loads of stuff. Most of it is here, some of it is still on its way. My Arduino Uno arrived around noon today, and I haven't been able to stop tinkering! It's really a lot of fun, and stupid easy even if you're not a programmer!

I started with the basic "oooh, blinking LEDs!" sort of projects (or "sketches" in Arduino parlance). Then I moved on to tweak those to work with multiple LEDs. There were a few different scenarios I ran though because two of my LEDs weren't lighting up very well. I guess I either hooked them up wrong or I didn't seat them properly... whatever.

The next project was when I hooked up a PIR (passive infrared) motion sensor ($9.99 at RadioShack) and installed a demo sketch that I found on the Arduino wiki. I wasn't quite sure how to wire everything for the PIR sensor, so I took a look at this Make Magazine video to see one way of setting up the circuit. That one simply lights up an LED and makes some noise. I just made mine light up and LED since I don't have a buzzer (yet).

Next I moved on to the LCD. The package came with a 20x4 green-on-black backlit LCD display, a 2.2k Ohm resistor, and a set of pins to connect the LCD to my breadboard or whatever. I actually soldered the pins onto the LCD display board (wow, was that even more difficult than I remember!) so I would have less of a hassle getting all of the contacts working. Getting the LCD to work was pretty easy after that.

Then I took those two projects a bit further. I'm certain others have done this years ago, but I didn't use a sketch that was completely written for me this time so I felt special enough to share :) I added the PIR circuit from before less the LED, merged pieces from both demo programs, and came up with a motion sensor that would flash "INTRUDER ALERT!!" on the LCD screen a few times when triggered. Based on my testing, the sensor works extremely well (once calibrated!!), and it will detect motion well across the open area of my apartment (maybe a bit further than 20 feet)!

I'm quite happy with my work, especially for not having "dealt with" electronics for such a long time. When I tried to share my success with some of my friends, they wanted a video to prove I'm awesome (I guess?). So here it is. Trust me, I know the video is horrible--I speak too quietly (baby sleeping in next room), I stumble over my words (that's just me), and I neglected to even offer you some music to rock out to as I blabber about this stuff.

Here's the program I wrote/modified:

 /*
  * //////////////////////////////////////////////////
  * //making sense of the Parallax PIR sensor's output
  * //////////////////////////////////////////////////
  *
  * Switches a LED according to the state of the sensors output pin. Determines
  * the beginning and end of continuous motion sequences.
  *
  * @author: Kristian Gohlke / krigoo (_) gmail (_) com / http://krx.at
  * @author: Josh VanderLinden / codekoala (.) gmail (@) com / http://www.codekoala.com
  * @date:   3 Jan 2011
  *
  * kr1 (cleft) 2006
  * Released under a creative commons "Attribution-NonCommercial-ShareAlike 2.0" license
  * http://creativecommons.org/licenses/by-nc-sa/2.0/de/
  *
  *
  * The Parallax PIR Sensor is an easy to use digital infrared motion sensor module.
  * (http://www.parallax.com/detail.asp?product_id=555-28027)
  *
  * The sensor's output pin goes to HIGH if motion is present. However, even if
  * motion is present it goes to LOW from time to time, which might give the
  * impression no motion is present. This program deals with this issue by
  * ignoring LOW-phases shorter than a given time, assuming continuous motion is
  * present during these phases.
  *
  */

 #include <LiquidCrystal.h>

 int calibrationTime = 10;   // seconds to calibrate PIR
 long unsigned int pause = 5000; // timeout before we "all" motion has ceased
 long unsigned int lowIn; // the time when the sensor outputs a low impulse

 boolean lockLow = true;
 boolean takeLowTime;

 int flashCnt = 4;  // number of times the LCD will flash when there's motion
 int flashDelay = 500; // number of ms to wait while flashing LCD
 int pirPin = 7;    // the digital pin connected to the PIR sensor's output
 int lcdPin = 13;   // pin connected to LCD
 LiquidCrystal lcd(12, 11, 10, 5, 4, 3, 2);

 void setup() {
     // Calibrates the PIR

     Serial.begin(9600);
     pinMode(pirPin, INPUT);
     pinMode(lcdPin, OUTPUT);

     digitalWrite(pirPin, LOW);

     clearLcd();

     // give the sensor some time to calibrate
     lcd.setCursor(0,0);
     lcd.print("Calibrating...");

     for(int i = 0; i < calibrationTime; i++){
         lcd.print(".");
         delay(1000);
     }

     lcd.print("done");
     delay(50);
 }

 void clearLcd() {
     // Clears the LCD, turns off the backlight
     lcd.begin(20, 4);
     lcd.clear();
     digitalWrite(lcdPin, LOW);
 }

 void alertLcd() {
     // Turns on the LCD backlight and notifies user of motion
     lcd.setCursor(2, 1);
     digitalWrite(lcdPin, HIGH);
     lcd.print("INTRUDER ALERT!!");
     lcd.setCursor(2, 2);
     lcd.print("================");
 }

 void loop() {
     // Main execution loop

     if(digitalRead(pirPin) == HIGH) {
         // flash an alert a few times
         for (int c = 0; c < flashCnt; c++) {
             alertLcd();
             delay(flashDelay);
             lcd.clear();
             delay(flashDelay);
         }

         if(lockLow) {
             // makes sure we wait for a transition to LOW before any further
             // output is made:
             lockLow = false;
             delay(50);
         }
         takeLowTime = true;
     }

     if (digitalRead(pirPin) == LOW) {
         clearLcd();

         if (takeLowTime) {
             // save the time of the transition from high to LOW
             // make sure this is only done at the start of a LOW phase
             lowIn = millis();
             takeLowTime = false;
         }

         // if the sensor is low for more than the given pause,
         // we assume that no more motion is going to happen
         if (!lockLow && millis() - lowIn > pause) {
             // makes sure this block of code is only executed again after
             // a new motion sequence has been detected
             lockLow = true;
             delay(50);
         }
     }
 }

Django-Tracking 0.3.5

I've finally gotten around to looking at a bunch of tickets that had been opened for django-tracking in the past year and a half or so. I feel horrible that it's really taken that long for me to get to them! Every time I got a ticket notification, I told myself, "Okay, I'll work on that this weekend." Many have weekends have passed without any work on any of my projects. I'm going to get better about that!

Anyway, several fixes have gone into the latest version of django-tracking. Some have to do with unicode problems (thanks ramusus!). Others have to do with overall performance, while yet others have to do with overall stability.

The first interesting change in this release is that django-tracking no longer relies on the GeoIP Python API. Instead it's now using django.contrib.gis.utils.GeoIP. I had hoped that this would remove the dependency on the GeoIP C API, but it appears that I was mistaken. Oh well.

Perhaps the biggest improvement in this new release is the use of caching. With caching in place, the middleware classes don't slam the database nearly as badly as they used to. There's still more that could be done with caching to improve performance, but I think what I've got now will be a big help.

Another noteworthy change, in my opinion, is the use of logging. I've sprinkled mildly useful logging messages throughout the code so you can learn when something bad happens that is silently handled. I hope that this will help me improve the quality of the code as it will allow anyone who uses the project (and pays attention to the log messages, of course) to tell me when bad things are happening.

Finally, the packaging code has been updated to be much more simple. Version 0.3.5 has been uploaded to PyPI and is available via pip or easy_install. If you prefer to have the latest copy of the code, the official code repositories are (in order of my personal preference):

I can't wait for your feedback!

Contextual Grepping

One of the tools I find myself using more and more each day is the amazing grep. It helps me narrow down the list of potential problem children in my code. Sometimes it can even tell me exactly where I need to look if my parameters are specific enough.

For example, the other day, I had a problem where some Python code was attempting to call isdigit() on an integer, when the variable was supposed to be a string. I could have scoured the code manually for all occurrences of the word "isdigit", or I could have used a "search in files" sort of feature in any useful text editor. There are likely other options too. However, I opted to use grep to find what I was looking for.

In the process of fixing this bug, I learned that grep offers the option of displaying a few lines of context around your matching text. There are a few ways you can tell grep to give you some context:

  • -A NUM, --after-context=NUM

    Print NUM lines of trailing context after matching lines. Places a line containing -- between contiguous groups of matches.

  • -B NUM, --before-context=NUM

    Print NUM lines of leading context before matching lines. Places a line containing -- between contiguous groups of matches.

  • -C NUM, --context=NUM

    Print NUM lines of output context. Places a line containing -- between contiguous groups of matches.

I thought this was so useful that I wrote a small shell script to wrap up my common options for grepping--recursive search, display line numbers, and (now) showing some context. Eventually I got around to cleaning up the output by dirtying up the script. Cleaning up the output involved only displaying a matching filename one time, with the line numbers for the context and matching lines below it. I also thought it would be easier to find matching lines if I could colorize the matched text. Here's my script as of noon today.

#!/bin/bash
# Recursively greps for some text in files in the current directory with some
# context lines.

GREEN=`echo -e '\033[41;30;1m'`
NORMAL=`echo -e '\033[0m'`
FIND=$1
grep --exclude=*.svn* --exclude=*.swp -rnC 5 "$FIND" * | \
    awk '{split($1, a, "-"); split(a[1], b, ":"); \
    if (b[1] != file) { file=b[1]; print file; } \
    sub(file, "", $0); print $0; }' | \
    sed -e "s/$FIND/$GREEN&$NORMAL/g;s/^[-\:]//g"

I'm sure there are ways to make this more elegant, but I'm sure happy with it. This little dandy assisted me just this morning in helping a friend resolve some Django bugs!

Here's a screenshot:

cgrep script in action

Tip: easy_install / pip

With all of the exciting updates to Mercurial recently, I've been on a rampage, updating various boxes everywhere I go. I'm in the habit of using easy_install and/or pip to install most of my Python-related packages. It's pretty easy to install packages that are in well-known locations (like PyPI or on Google Code, for example). It's also pretty easy to update packages using either utility. Both take a -U parameter, which, to my knowledge, tells it to actually check for updates and install the latest version.

That's all fine and dandy, but what happens when you want to install an "unofficial" version of some package? I mean, what if your favorite project all of the sudden includes some feature that you will die unless you can have access to it and the next official version is weeks or months in the future? There are typically a few avenues you can take to satisfy your needs, but I wanted to bring up something that I think not many people are aware of: easy_install and pip can both understand URLs to installable Python packages.

What do I mean by that, you ask? Well, when you get down to the basics of what both utilities do, they just take care of downloading some Python package and installing it with the setup.py file contained therein. In many cases, these utilities will search various package repositories, such as PyPI, to download whatever package you specify. If the package is found, it will be downloaded and extracted.

In most cases, you can do all of that yourself:

$ wget http://pypi.python.org/someproject/somepackage.tar.gz
$ tar zxf somepackage.tar.gz
$ cd somepackage
$ python setup.py install

Both easy_install and pip obviously do a lot of other magic, but that is perhaps the most basic way to understand what they do. To answer that last question, you can help your utility of choice out by specifying the exact URL to the specific package you want it to install for you:

$ easy_install http://pypi.python.org/someproject/somepackage.tar.gz
$ pip install http://pypi.python.org/someproject/somepackage.tar.gz

For me, this feature comes in very handy with projects that are hosted on BitBucket, for example, because you can always get any revision of the project in a tidy .tar.gz file. So when I'm updating Mercurial installations, I can do this to get the latest stable revision:

$ easy_install http://selenic.com/repo/hg-stable/archive/tip.tar.gz

It's pretty slick. Here's a full example:

[user@web ~]$ hg version
Mercurial Distributed SCM (version 1.2.1)

Copyright (C) 2005-2009 Matt Mackall <mpm@selenic.com> and others
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
[user@web ~]$ easy_install http://selenic.com/repo/hg-stable/archive/tip.tar.gz
Downloading http://selenic.com/repo/hg-stable/archive/tip.tar.gz
Processing tip.tar.gz
Running Mercurial-stable-branch--8bce1e0d2801/setup.py -q bdist_egg --dist-dir /tmp/easy_install-Gnk2c9/Mercurial-stable-branch--8bce1e0d2801/egg-dist-tmp--2VAce
zip_safe flag not set; analyzing archive contents...
mercurial.help: module references __file__
mercurial.templater: module references __file__
mercurial.extensions: module references __file__
mercurial.i18n: module references __file__
mercurial.lsprof: module references __file__
Removing mercurial unknown from easy-install.pth file
Adding mercurial 1.4.1-4-8bce1e0d2801 to easy-install.pth file
Installing hg script to /home/user/bin

Installed /home/user/lib/python2.5/mercurial-1.4.1_4_8bce1e0d2801-py2.5-linux-i686.egg
Processing dependencies for mercurial==1.4.1-4-8bce1e0d2801
Finished processing dependencies for mercurial==1.4.1-4-8bce1e0d2801
[user@web ~]$ hg version
Mercurial Distributed SCM (version 1.4.1+4-8bce1e0d2801)

Copyright (C) 2005-2009 Matt Mackall <mpm@selenic.com> and others
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Notice the version change from 1.2.1 to 1.4.1+4-8bce1e0d2801. w00t.

Edit: devov pointed out that pip is capable of installing packages directly from its repository. I've never used this functionality, but I'm interested in trying it out sometime! Thanks devov!

Automatic Config Replication With Mercurial

I've done a lot of neat things since I started my new job earlier this month. I'm really excited about the things I've learned and experimented with, and I would like to share some of the concepts with my visitors.

At work we use a lot of virtual machines in our individual development environments. Most of these virtual machines use very similar configuration settings, but the settings are not a standard part of the installation. That is because we build our virtual machines using the same installation tools that our customers would use. The configuration I'm talking about is just stuff specific to our development environment.

Creating and configuring these virtual machines is one of the first things my mentor showed me how to do my first day on the job. He commented on how quickly I would probably start learning all of the configuration tasks because we tend to setup our development VMs several times a month. That was all fine and dandy, and I did get a pretty good feel for what needed to go into a development VM that first day.

However, after doing it so many times, I realized how much time I was using just trying to get the VM set up just right. It wasn't hard to configure--it was just time-consuming. It wasn't long before I started thinking of ways to optimize the process.

One of the ideas I came up with, which seems to be serving my purposes perfectly, is that of using Mercurial to quickly and easily get the exact same configuration from one box to another. It also has the added benefit of keeping a history of the changes I make to my configuration as time goes on.

I won't go into exact detail on how I have things setup at work, but I would like to try to describe a similar scenario that should illustrate my goal just as well.

Getting Started

One of the first things I would encourage you to do is follow along. It will make the concept sink in much faster, and you will probably see other applications very quickly. Please note, however, that if you're following along exactly, it could be a very time-consuming process. I will be using 3 virtual machines as I write this, but you could just as easily use 5, 10, or 100,000. Likewise, you could eliminate the virtual machines altogether if you're in an environment with several physical computers.

One virtual machine will act as the "master" server, or the one that will be configured first. The other virtual machines will act as "slave" servers, which will simply receive configuration updates that happen on the master server. We will also modify this behavior to be a bit more interesting toward the end of the article.

Virtual Machines Galore!

First off, I will create some basic virtual machines using the net install version of Debian 5.0.3. I really only need to create 1 VM and then clone it a couple of times. I am willing to furnish my virtual machines to those who are interested in using them. I will install some additional software in the VM to make sure the demo works smoothly. Among the packages that I will install are:

  • Python
  • Mercurial
  • OpenSSH server

Initialize a Repository

Once I have all of that set up in my virtual machines, I will initialize a Mercurial repository on the master server to maintain the configuration files that I am interested in. Let's just use the /etc directory for the time being. There's a pretty good chance that most of our system-wide configuration will all be contained somewhere beneath /etc.

cd /etc
hg init

Now let's have a gander at the files that we can have Mercurial manage for us:

hg st

Wow! That is quite a set of files, isn't it? Thankfully, they should mostly be plain text files. Mercurial is very efficient at managing text files. Let's now add all of the files in /etc to our repository, so they can be tracked and easily pushed out to other systems.

hg add

That command will happily add everything that hg st printed. Obviously, we can get a little more picky about what we do and do not add to our repository, but that's not the goal of this article. Now, this step merely tells Mercurial that it needs to pay attention to changes in these files. The files have not yet been committed to the repo. Let's do that, so we have a backup of our configuration files in their pristine state:

hg ci -m "Initial import"

The -m "Initial import" is just a comment, to describe what happened to warrant a commit to the repository. It is for your use and the use of anyone who has access to your repo.

Clone The Configuration

Now let's try to push the configuration we just committed on the master server to one of the slave servers. Since my virtual machines are all essentially in the same state, there should be no conflicts, right? Try running the following command on the master server:

hg push ssh://root@slave1//etc
root@slave1's password:
remote: abort: There is no Mercurial repository here (.hg not found)!
abort: no suitable response from remote hg!

Blast! We can't simply push the configuration files out to another computer. For that to work, we'd first have to have the repository itself exist on the slave server. Let's try this another way. One the slave server, run this command:

hg clone ssh://root@master//etc /etc
root@master's password:
abort: destination '/etc/' is not empty

Doh! Mercurial won't let us clone the repository from the master server! That's because Mercurial wants to clone to a new directory, with nothing already in it. One way to get around this hairball of a show-stopper is to just copy the repo using conventional UNIX utilities. Execute this command on one of your slave servers:

scp -r root@master:/etc/.hg /etc/

The .hg directory contains all of the repository information, and it's really all we need to snag in order to clone the repository. This might not be the most elegant solution in the world, but it will suffice for the time being. Once the scp command completes, we should have a full copy of the configuration file repository. Run this command to verify:

hg st

If your setup is anything like mine, you'll probably have a few files that are listed as being modified. Chances are that these files will vary from host to host anyway, and they are probably not worth keeping in a version control system. That would just be begging for conflicts.

I wrote an extension for Mercurial that should make this part of my tutorial a little less hacky. On your other slave server, run the following commands:

hg clone http://bitbucket.org/codekoala/hgext /root/hgext
echo "[extensions]" >> /root/.hgrc
echo "neclone = /root/hgext/neclone.py" >> /root/.hgrc

This extension gives you a new Mercurial command called neclone (N. E. Clone, or "not empty clone"). As we saw earlier, Mercurial doesn't let us clone a repository into a directory that is not empty. This extension allows us to do that. It works almost identically to the regular clone command... takes the same options and everything.

Still on your second slave server, run these additional commands:

hg neclone ssh://root@master//etc /etc
cd /etc
hg up -C

The last step is optional, and soon to be included as part of the extension. It will update your working copy to the latest revision in the repository. Beware that it overwrites any uncommitted changes you may have made to files that are tracked by Mercurial.

So now both slave servers should have a clone of the configuration repository from the master server.

Being Picky

Let's start to be a little picky about the files we are tracking in our repository. Some of the files appears as being modified on my slave server after copying the .hg directory from the master server are:

  • adjtime
  • alternatives/pager
  • alternatives/pager.1.gz
  • mailcap
  • network/run/ifstate
  • udev/rules.d/70-persistent-net.rules

I think it's safe to remove these from the repository, to avoid conflicts with other systems. To tell Mercurial to stop tracking files it is tracking, without actually deleting the file from the filesystem, you can use the following command:

hg forget adjtime
hg forget mailcap

And so on. Go ahead and do that for each of the files that appeared to be modified on your slave server immediately after copying the .hg directory. I'm going to add /etc/hostname to the list of files to forget too.

After doing that, each of those files should appear as being marked for removal when you run hg st. Don't worry, this is normal. The files will not be deleted from the filesystem, but they will be deleted from the repository. Go ahead and commit those changes to the repository on your slave server.

hg ci -Am "Removed some files from version control"

Now let's push those changes out to the master server:

hg push
abort: repository default-push not found!

Since we copied the .hg directory directly using scp, our slave won't know where the changes need to go when we run the push command with no explicit destination repository. To fix that, let's create a file in /etc/.hg/ called hgrc on the slave server. In that file, put the following text:

[paths]
default = ssh://root@master//etc

The hg push command should now push directly to the master server. Yay! The problem we face now is that every other slave server in the group is out of date. How can we fix that? We'll use Mercurial hooks.

Automating Config Replication

Mercurial offers some very useful hooks that we can use to automatically push configuration changes out to each of our slave servers. We will use the commit and changegroup hooks to do the magic. Let's create a script that will live on the master server to take care of pushing our changes out to each slave server. Create a new file in /etc/ on the master server called propagate.sh:

#!/bin/bash
hg up
for node in 'slave1' 'slave2'
do
    ssh root@$node "cd /etc; hg pull -u"
done

Let's also make sure this script is executable:

chmod +x /etc/propagate.sh

This script assumes that your /etc/hosts file or your nameserver are configured appropriately to allow slave1 and slave2 to be resolved to IP addresses. The reason we're SSH'ing into each slave server and using hg pull instead of simply using hg push ssh://root@$node//etc is because you can't force an update on a remote server using push. You can, however, request an update when you're using pull.

Obviously, this script is not the most sophisticated of scripts. It might work well for my demonstration, with only a few servers, but once you get beyond that it would be a nightmare to maintain the list of servers the script has to connect to. You can use whatever means you'd like to keep track of the servers you want to replicate your configuration to. I don't want to bother with all of the crap I'd get for suggesting one thing over another, so it's now your call.

Now it's time to configure the Mercurial hook to execute that script when the master server sees a changeset get into its repository. Open up /etc/.hg/hgrc on the master server, or create it if it doesn't exist. Make sure it has at least the following in it:

[hooks]
commit.propagate = /etc/propagate.sh
changegroup.propagate = /etc/propagate.sh

Let's try it out! Run these commands on your master server:

echo "" >> /etc/hosts
hg ci -m "Added a blank line to the hosts file"
root@slave1's password:
remote: Permission denied, please try again.
remote: Permission denied, please try again.
remote: Permission denied (publickey,password).
abort: no suitable response from remote hg!
Connection closed by slave2
warning: commit.propagate hook exited with status 255

Blast! The script failed because it wanted us to type in a password, but it was not in interactive mode. Let's fix that with a little preshared key magic. I won't go into the details about how this works, but the following commands on your master server should get us rolling:

ssh-keygen
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys2
scp -r ~/.ssh root@slave1:~
scp -r ~/.ssh root@slave2:~

Warning

Keep in mind this is not secure and should probably not be how your production machines are configured, especially with the root user.

For simplicity's sake, just accept all of the details and don't set a passphrase. These commands enable us to SSH into our slave servers without using a password. If you get an error such as:

remote: Host key verification failed.
abort: no suitable response from remote hg!

...it just means you need to manually log into your master server from the slave machine that threw that error. When doing so, you will have to answer "yes" to a question about the authenticity of the host you're logging into.

Testing It Out

It is now time to see if we can make a configuration change on one slave server and have it show up on the other slave server. Let's update the hosts file a little bit. Let's add the following line on the second slave server:

10.0.0.5        nonexistanthost

Now let's commit the change and push it off to the master server:

hg ci -m "Added a dumb line to the hosts file"
hg push

My system actually told me that that it had copied the change out to another host. I know because I saw these lines:

remote: pulling from ssh://root@master//etc
remote: searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files

Now when I look at the first slave server, I should see that new line in my /etc/hosts file. Also, the log on each server should have the same entry that I just made about adding "a dumb line to the hosts file."

Seem Like A Lot of Work?

A lot of what we just did probably seemed like more work that it is worth, right? Well, being a nerd typically comes with a few qualities. One quality which I have observed many a time in my most geeky of friends is that they will spend hours and hours up front on a program or script just so they can save 2 minutes in the future. They work hard to be lazy.

There is a lot of boilerplate configuration that takes place in this particular scenario. I realize that. What I haven't shared with you, though, is how I automated the boilerplate configuration as well as the propagation of configuration. I'm tired of putting this article off, so I will have to leave those details for another article. Sorry!

Why?! There's a Better Way (tm)

There is always a better way. Always. Go ahead and use whatever you feel is the most efficient method for keeping configuration files in sync across several computers. This is just one more option to add to your toolkit. Don't worry, I won't be offended if you don't like it or don't use it. It works perfect for me and it's free, and I just wanted to share!

Bulk Update With Mercurial

Some of you may well know that I was previously an subversion user, more out of comfort than necessity. SVN was the first version control system that I became well acquainted with, so it just seemed like a natural choice for me when I thought I needed version control.

Several months ago I read a blog article by a buddy, in which he briefly discussed Mercurial. I had been meaning to give some distributed version control systems a shot after some disasters related to the centralized nature of SVN. This blog article prompted me to take a stab at Mercurial and some others.

Within a few days I was sold on Mercurial. I won't go into details simply because I'm not one for religious wars that way. Let's just say that Mercurial seemed to be perfect for my wants and needs.

There were, however, a few things about using Mercurial that I miss from the SVN world. One such thing is that you can update several "working copies" of something in SVN with a single command. For example, I keep a lot of my 3rd party Django applications in one directory. Many of these applications use SVN. Sometimes I'll just run a command like this:

svn up /path/to/third/party/apps/*

Each project that uses SVN will automatically be updated without much fuss with such a command. However, with Mercurial, it appears that you need to be in an actual Mercurial repository in order to update it. There are extensions to get around this problem, but I was looking for something a little different.

Since I use Linux almost exclusively, I didn't feel bad about just using the power within to do the work. The following command does everything I need it to:

find -name ".hg" -type d | xargs -t -i bash -c "(cd {}; hg pull; hg up)"

This command finds any directories called .hg anywhere under your current location on the filesystem. Any matches will be used in the command at the end: cd {}; hg pull; hg up

So far I haven't had any problems with this command, but your mileage may vary. To make things even easier, I made an alias for this rather long command:

alias hgupall='find -name ".hg" -type d | xargs -t -i bash -c "(cd {}; hg pull; hg up)"'

I put that line in my ~/.bashrc script, which is executed each time I log into my computer. With that in place, all I need to do is something like this:

cd /path/to/third/party/apps
hgupall

And the aliased command handles the rest. Pretty slick stuff. Hooray for Mercurial and Linux!

Mercurial 1.3 Released

Today marks the official release of Mercurial 1.3, an awesome distributed version control system. This release comes with several nifty features, including the following, straight from the What's New wiki page:

Major Changes

  • experimental support for sub-repositories
  • Python 2.3 is no longer supported; now requires Python 2.4-2.6

Commands

  • merge: add -P/--preview option
  • update: don't unlink added files when -C/--clean is specified
  • update: added -c/--check option to abort on local changes
  • update: allow merges going backwards
  • push: improved handling of named branches
  • branches/heads: add a -c/--closed option to show closed branches
  • help: new extensions topic

General

  • add patch.eol config setting to work with cross-platform patches
  • fixed support for SSL through proxies
  • add ability to load hooks from arbitrary Python modules
  • hide passwords for HTTP repositories in error and log output
  • fix Python 2.6 support in the Windows installer
  • add mechanism for specifying HTTP authentication details in hgrc
  • prompts and choices are now shown even in non-interactive mode
  • performance improvements, especially on Windows
  • much improved zsh completion
  • improved Danish, Japanese, Italian and simplified Chinese translations
  • new German, French, Greek, Brazilian Portuguese and traditional Chinese translations

Web interface

  • read configuration data from webdir configs
  • add branches page to hgweb
  • pluggable templater engine support
  • refresh hgwebdir configuration periodically
  • let web.encoding override ui.encoding setting
  • deal with dicts/lists like webdir config paths

I'm quite stoked about this release :) For additional information, please check the project's wiki.

My VIM Adventures

Along with my recent adventures with Fedora 11, I decided to force myself to become more proficient with VIM. For those of you who do not know, VIM is based on perhaps one of the oldest surviving text editors around today. There are often religious-grade battles between those who believe in VIM and those who believe in Emacs, another long-surviving text editor. I'm not trying to get into any debates about which is better, and I'm not interested in why I should not be using VIM. If you still feel like I need to be set straight, please use the contact me form instead of the comments section.

Anyway, most people who use these editors fall into 1 of 3 categories (there are probably more categories actually):

  1. They're familiar with it enough to get the job done, but they're not exactly proficient. Therefore, they don't care about evangelizing the editor.
  2. They're proficient with the editor, but they're afraid of the politics involved in religious wars relating to text editors, so they don't evangelize.
  3. They're proficient with the editor and feel that the whole world would be better off if everyone used their preferred text editor. As such, they cannot shut up about the dang thing and drive all of their friends, coworkers, and acquaintances mad.

A few of you will probably agree with what I'm about to say. I fear I have transitioned from stage 1 to stage 3 fairly rapidly. I can't stop talking about VIM all of the sudden! You'd think it's the next best thing after sliced bread the way I've been blabbering about it. And here I am, writing an article about it. Hah.

Ever since I first started using Linux, I have been using vi to handle most of my text editing when I was in a terminal. I knew enough to get around. Basic things like navigation and inserting text were pretty much all I knew how to do. I dabbled with a tutorial here and there, but it wasn't long before the things I learned were lost, since I usually preferred a graphical text editor over VIM.

My recent experimentation with VIM has proved to be very fruitful, if I do say so myself. I am no longer tied down to some editor that is slow and bulky, I don't have much to worry about when I switch computers (chances are that VIM is on any computer I use regularly), and I don't even need to be sitting at the computer I'm using VIM on! In fact, today I was doing most of my work over an SSH session to my netbook. I felt more productive today than I have in a very long time.

It's been a long time since I've enjoyed using a mouse to perform basic tasks on my computer. Using VIM allows me to rid myself of the mouse entirely for my text editing tasks, and I don't feel at all limited in my capabilities. Things that used to be quite sketchy operations using my favorite graphical editors end up being very simple with VIM.

I also love the obscurity favor of it all.

Examples

I wish I could just keep adding stuff to this list! There are so many neat things I want to share with everyone about VIM! I'm sure there are more efficient ways to do some of the things I have been learning with VIM, but this works very well for me.

Laziness

I do a lot of reStructuredText for various things. In fact, I'm writing this article using VIM right now. ReST is fantastic, but it's horrible to do using an editor that is not set up with a mono spaced font. I like to see things nicely lined up (I'm a Python developer, after all). I also like to have my section headings have an underline that is as long as the heading itself. For example, the heading just above this looks like this:

Examples
========

In this particular instance, it's not a big deal to hold down the equals key long enough to underline the word "Examples". However, sometimes I get some pretty lengthy section titles. The lazy side of me doesn't want my finger to hang around on the same key for very long (or tap it dozens of times, for that matter). Also, trying to figure out how many characters are in a section title without a mono spaced font is very annoying.

The/a solution? Say I have a section heading that is 50 characters long. To underline it, all I have to do is type 50i= and hit the escape key.

Cutting Text Mid-Line

Another neat thing is being able to cut text from the cursor to a particular character somewhere later on (or earlier on!) in the same line. Say I have a hyperlink whose address I wish to change:

<a href="http://www.somelong.com/that/I/want/to/change/">Link Text</a>

Instead of using the mouse to highlight the href attribute's value (or highlight it using shift on the keyboard), I just position my cursor on the h in http and type dt". VIM will lop that address right out of there (and you can paste it elsewhere if you'd like). I used this particular shortcut countless times today as I replaced things like {% url some-named-url with,some,parameters %} with {{ some_object.get_absolute_url }} in some Django templates.

Search & Replace

And I cannot neglect the classic search and replace functionality in VIM. You can use fancy regular expressions in VIM to replace some text with something else. I was trying to do a little refactoring today, and I came up with a command like this:

:s/something/lambda (a,b,c): \0(a,b,c)/g

That sort of command works great to replace all occurrences of "something" on the current line with "lambda (a,b,c): something(a,b,c)". Fantastic. What about a global search and replace, instead of just the current line? Stash a % at the front of the command (:%s/something/lambda (a,b,c): \0(a,b,c)/g) and you're in business.

Now what if you only wanted to perform that search and replace over a certain group of lines instead of a single line or the whole file? This is one I'm particularly thrilled about:

:.,.+9 s/something/lambda (a,b,c): \0(a,b,c)/g

That little beauty will perform the search and replace on the current line and the following 9 lines. How awesome is that?

Moving & Deleting Words

Sometimes as I am writing something, I decide I would like to reword a sentence as I near the end. Sometimes this involves simply deleting a word or two. Sometimes it means chopping a few words out of the beginning part of a sentence to put them back at the end somewhere. Whatever the case, VIM seems to handle my needs perfectly well.

Say I have this sentence (from the Vimperator Web site): "Writing efficient user interfaces is the main maxim, here at Vimperator labs." If I want to move the "here at Vimperator labs" to the beginning of the sentence, assuming I just finished typing it, I would place my cursor over the period at the end, type dT,, hit ( to go to the beginning of the sentence, hit P to insert what I just copied, and then handle the rest of the clean up (capitalization, fixing the comma, etc). I could have also done something like, 4db instead of dT,.

If I want to cut/delete an entire word, or to the end of whatever word my cursor is currently on, I could use dw. For more than one word, just put a number before the command. It's great stuff!

Taking It Too Far

I've gotten so carried away with all of this VIM business. I really have. I installed vimperator in Firefox. This extension gives Firefox a VIM-like interface. Now I can do pretty much all of my regular surfing without using the mouse. Some may argue that this is absolutely impractical because it would take much longer to get to the right link on a page using the keyboard than it would with the mouse. That may be true. I dunno, but I still think it's awesome that I really don't need my mouse to browse the Internet now.

As I was playing with vimperator tonight, one of my buddies pointed out another useful extension called It's All Text. This extension allows you to use your preferred text editing program in regular old text boxes in Firefox. It is this extension which has just made writing my blog articles 200x more efficient. Now I can quickly and easily write my articles right here in VIM without having to copy and paste all over the place. Pretty dang incredible.

Oh yes, I'd like to thank Chad Hansen and Jonathan Geddes for helping me out as I explore the depths of VIM. You guys rock!