Tuesday, October 12, 2010

Headless VirtualBox

This being the second time I've set this stuff up, I thought it's worth documenting my VirtualBox development workflow.

A Decent Hacking Environment

The OSX side of my laptop is working pretty smoothly. I've got my stack of tools configured to my liking, from my shell, to my editor, to my documentation browser. I've spent years cargo culting all my dotfiles.

But pick my brain any day and I'll give you a mouthful about Apple and OSX. I also know that there are superior alternatives to most of my software stack.

That said, even if I'm not entirely happy with the setup, I'm definitely content with it, and I have no plans on learning anything new to gain a 3% efficiency in the way I type in text or customize the way I waste time online.

Part of the reason I use OSX is that there is no hope (and therefore no temptation) in trying to fix little annoyances, something that led me to sacrifice countless hours during the brief period of time when I had a fully open source desktop environment.

However, when it comes to installing and configuring various project dependencies (daemons, libraries, etc), OSX can be a real pain compared to a decent Linux distribution.

A Decent Runtime Environment

Disk space is cheap, and virtualization has come along way in recent years, so it really makes a lot more sense to run my code on a superior platform. One image per project also gives me brainless sandboxing, and snapshots mean I can quickly start over when I break everything.

Sweetening the deal even more, I always seem to be surrounded by people who know how to properly maintain a Debian environment much better than I could ever hope to, so I don't even have to think about how to get things right.

Bridging the Gap

In order to make it easy to use both platforms simultaneously, with cheap context switching (on my wetware, that is), I've written a script that acts as my sole entry point to the entire setup.

I don't use the VirtualBox management GUI, and I run the Linux environment completely headless (not just sans X11, without a virtual terminal either).

I hard link the following script in ~/bin, once per VM. To get a shell on a VM called blah, I just type blah into my shell prompt and hit enter:


VM="$( basename "$0" )"

if [ -n "$1" ]; then
    # explicit control of the VM, e.g. `blah stop`
    # useful commands are 'pause', 'resume', 'stop', etc
    case "$1" in
        status) VBoxManage showvminfo "$VM" | grep -i state ;;
        *)      VBoxManage controlvm "$VM" ${1/stop/acpipowerbutton} ;; # much easier to type
    # otherwise just make sure it's up and provide a shell

    # boot the virtual machine in headless mode unless it's already running
    # note that there is a race condition if the machine is in the process of
    # powering down
    VBoxManage showvminfo --machinereadable "$VM" | grep -q 'VMState="running"' || \
    VBoxManage startvm "$VM" -type vrdp;

    # each VM has an SSH config like this:

    # Host $VM
    #     Hostname localhost
    #     Port 2222 # VBoxManage modifyvm "$VM" --natpf1 ...

    # changing ssh port forwarding doesn't require restarting the VM (whereas
    # fiddling with VirtualBox port forwarding does). The following section
    # should probably just be a per VM include, but for my needs it does the
    # job as is.

    # ControlMaster works nicely with a global 'ControlPath /tmp/%r@%h:%p' in
    # my ~/.ssh/config this means the port forwarding stays up no matter how
    # many shells I open and close (unlike ControlMaster auto in the config)

    # this loop quietly waits till sshd is up
    until nc -z localhost 3000 >/dev/null; do
        echo -n "."
        ssh -N -f -q \
            -L 3000:localhost:3000 \
            -o ConnectTimeout=1 \
            -o ControlMaster=yes \
            "$VM" && echo;

    # finally, start a shell
    exec ssh "$VM"

Once I'm in, I also have my code in a mount point under my home directory. I set up a shared folder using VirtualBox's management GUI (installing the VirtualBox guest additions like this). To mount it automatically I've got this in /etc/fstab on the guest OS:

# <file system>  <mount point>               <type>  <options>                       <dump> <pass>
some_dir         /home/nothingmuch/some_dir  vboxsf  uid=nothingmuch,gid=nothingmuch   0      0

I use the same path on the OSX side and the Linux side to minimize confusion. I decided not to mount my entire home directory because I suspect most of my dotfiles aren't that portable, and I'm not really running anything but Perl and services on the debian side.

I use all of my familiar tools on OSX, and instantly run the code on Debian without needing to synchronize anything.

When I'm done, blah stop will shut down the VM cleanly.

Finally, as a bonus, my bash prompt helps keep my confusion to a minimum when sshing all over the place.


John Napiorkowski said...

How do you find having the VM drivers affect battery life (if at all?) I'm also on a macbookpro and although in general I find there's little I can't install there is a use for me to have a real linux server I can test on when I am not online, but I've hesitated to install virtualbox out of fear it will impact my battery too much.

Anonymous said...

Don't you need to setup ControlPath also in your ssh ControlMaster call?

Maybe you have Host * with that definition?

nothingmuch said...

@pedro: Yeah, I have Host * with ControlPath in ssh_config, but ControlMaster is still disabled by default.

This way I start a shell with -o ControlMaster=yes explicitly when I know it will be open in the background for all subsequent sessions.

This lets me open and close shells without having to remember which is the first one (under ControlMaster=auto if I close that instance of ssh all my other sessions go down with it as well).