Automating local hosts

Setting up a new local host is a familiar task to most web developers, updating /etc/hosts to include yet another local domain, and potentially updating Apache config to recognise the new server. This process can be made much easier in several ways.

Apache configuration

The easiest way to not have to deal with basic Apache configuration again is to put all your local domains in one basket; in my case ~/Sites. We can then add the following to our Apache config…

NameVirtualHost *:80

<VirtualHost *:80>
    VirtualDocumentRoot /Users/username/Sites/%0

    ServerName localhost

    <Directory /Users/username/sites/>
        Order deny,allow
        Allow from all
        Options Indexes MultiViews FollowSymLinks
    </Directory>
</VirtualHost>

This tells Apache to look for a directory in /Users/username/Sites matching the incoming domain name. So, for example, you would put the document root for myproject.dev in /Users/username/Sites/myproject.dev, and serve all your files from there (though personally I tend to symlink them to a separate local repository instead).

I put this in /extras/httpd-sites.conf (along with various other configurations for slightly more advanced local development — wsgi etc), and include it at the bottom of httpd.conf, but your setup may be different, of course.

DNS

With that done, Apache now knows where to find our document roots, but our browsers don’t know where to find Apache. There’s nothing in the system that actually says “this server is actually on my machine”. If you wanted to go full-on you could configure your DNS to direct all *.dev or local.* traffic to 127.0.0.1 with a tool like Dnsmasq, but most developers seem to be much happier editing their /etc/hosts file.

Knowing where all of our document roots live makes this process relatively easy to automate. launchd is a service-management framework used to start, stop and manage daemon, applications, processes, and scripts in Apple OS X environments, and it’s exactly what we need to keep an our on our ~/Sites folder.

Using launchd

launchd is itself managed by launchctl, which (amongst many other tasks) reads in property lists and adds them to launchd.

The two important bits we need to know about launchctl configuration are Program (or ProgramArguments) and QueueDirectories. The Program key maps to the name of the program to run whenever the job is started. The QueueDirectories key provides a list of directories to watch, and causes the job to be started if any one of the listed paths are modified. Applying these properties gives us something like this.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>Label</key>
        <string>net.andrewhayward.generate-hosts</string>
        <key>Program</key>
        <string>/path/to/script</string>
        <key>QueueDirectories</key>
        <array>
            <string>/Users/username/Sites</string>
        </array>
    </dict>
</plist>

This essentially says that whenever /Users/username/Sites is modified, run the script at /path/to/script. Because this requires administrative privileges, and (theoretically at least) to run without a user being logged in, we need save this file in /Library/LaunchDaemons. Having done that, we just load it into launchd using launchctl.

> sudo launchctl load /Library/LaunchDaemons/net.andrewhayward.generate-hosts.plist

Automation

Of course, that’s all well and good, but we need something to actually put at /path/to/script. You can either write your own hosts processor, or you can save time and use mine instead. It’s relatively self-explanatory — it generates a hosts configuration file — and it’s probably riddled with bugs, but it seems to do the job. I won’t go over every option generate-hosts.py offers (you can read them yourself!), but in brief:

  • -u says that we should update hosts that already exist
  • -s says that we should stay silent
  • -w says that we should write the file back to where we found it
  • -d says that we should read folders from the specified target directory

If you do use mine, we’ll need to modify our property list a little. Rather than use the Program key, we instead use ProgramArguments, which is essentially a slightly more advanced Program that takes arguments.

<key>ProgramArguments</key>
<array>
    <string>/path/to/generate-hosts.py</string>
    <string>-u</string>
    <string>-s</string>
    <string>-w</string>
    <string>-d /Users/andrew/Sites</string>
</array>

In conclusion

So that’s it, really. We set up Apache to check for anything thrown at it in a single location, we set up the operating system to keep an eye on that location and to let us know when it changes, and we wrote a script that reads a directory for folder names and generates a hosts file from them.

All of this means that you need never think about setting up local hosts again. Whenever you make a new folder in ~/Sites, your computer will automatically map it to a local host, and know to use it as a document root for that new server.

Even if you only set up a new local host once a year, you can easily afford the time it will take you to set all of this up!

Your email address will not be published. Required fields are marked *

HTML is not allowed (it'll appear as typed), but you can use Markdown instead. For the unitiated, paragraphs will be auto-inserted, but links won't be converted unless you [write them properly](http://example.com/).