Using Phing To Create Apache Virtual Hosts

Phing is an awesome tool for automating things and I use it more and more for automating all kinds of different tasks. One of the tasks that I don't tend to do all that much is setting up a new local virtual host for Apache on my development machines. I know how to do it, but there is always something I forget to do, or a convention that I don't follow which means that I have to repeat myself at a later date to fix something I have missed.

So to make my life a little easier I decided to create a Phing build file to automatically create a virtual host and the everything associated with it in one go. Essentially, I would need to do the following tasks:

  • Create a virtual host directory.
  • Set up the virtual host directive in Apache.
  • Add an additional entry to the hosts file.
  • Restart the Apache server.

When creating a virtual host I would need to create it with some sort of name, and rather than hard coding the parameter before the build file is run I decided to use a propertyprompt task. This task will pause execution of the build file until the user has entered some text into the command window. The task is used to set the sitename property, which will be used later on in the process.

<propertyprompt propertyName="sitename" defaultValue="" promptText="Enter site name" />

Of course, we want to have some error detection here, so a simple if statement prevents the build file from running if the user hasn't entered anything. If the user has entered something into the prompt then we continue on by calling the createsite phing target.

<if>
  <equals arg1="${sitename}" arg2="" casesensitive="false" />
  <then>
    <echo>No site entered, quiting.</echo>
  </then>
  <else>
    <phingcall target="createsite" />
  </else>
</if>

We are nearly ready to start building the target that creates the virtual hosts, but what I wanted from this build file was to be able to run it on multiple systems without having to alter the build file or ask another question about what system setup they were using. This is essential for me as I tend to work a lot on Linux and Windows platforms. So I needed an automatic way of detecting the operating system in use and acting accordingly. This means that I can just put the build file in my Dropbox folder (or similar) and run it from whatever system I'm using at the time.

The built in Phing parameter host.os can be used here to determine the system type in question. If the system is Linux then the createlinuxsite target is called, and if the system is Windows (called WINNT in the host.os parameter) then we call the createwindowssite target. What we are left with looks a little complicated, but it is essentially a nested if statement that calls either one target or another, or just printing out a message if the host isn't found.

<propertyprompt propertyName="sitename" defaultValue="" promptText="Enter site name" /> 
<if>
  <equals arg1="${sitename}" arg2="" casesensitive="false" />
  <then>
    <echo>No site entered, quiting.</echo>
  </then>
  <else>
    <if>
      <equals arg1="${host.os}" arg2="Linux" casesensitive="false" />
      <then>
        <phingcall target="createlinuxsite" />
      </then>
      <elseif>
        <equals arg1="${host.os}" arg2="WINNT" casesensitive="false" />
        <then>
          <phingcall target="createwindowssite" /> 
        </then>
      </elseif>
      <else>
        <echo>Host type "${host.os}" not found.</echo> 
      </else>
    </if>
  </else>
</if>

Before we can go about creating the build targets we must first create a file called vhost.conf, this will be a default vhosts file that will be used to generate the virtual host entries in the Apache configuration. The following is the default vhost configuration from Ubuntu with some minor differences. The string #### is used to replace the name of the virtual host we enter when the build file is run.

<VirtualHost *:80>
	ServerName ####.local
	ServerAlias www.####.local	
	ServerAdmin [email protected]

	DocumentRoot /var/www/####/public_html/
	<Directory />
		Options FollowSymLinks
		AllowOverride All
	</Directory>
	<Directory /var/www/####/public_html/>
		Options Indexes FollowSymLinks MultiViews
		AllowOverride All
		Order allow,deny
		allow from all
	</Directory>

	ErrorLog ${APACHE_LOG_DIR}/####_error.log

	# Possible values include: debug, info, notice, warn, error, crit,
	# alert, emerg.
	LogLevel warn

	CustomLog ${APACHE_LOG_DIR}/####_access.log combined
</VirtualHost>

Creating the Linux target is done by following the instructions I detailed at the start of the post. Here is the resulting target.

<target name="createlinuxsite">
  <!-- Target for creating a linux virtual host -->
  <echo>Creating www.${sitename}.local</echo>
  <!-- create site directory -->
  <mkdir dir="/var/www/${sitename}/public_html" />

  <!-- write vhosts file with the information -->
  <copy file="linuxvhost.conf" tofile="/etc/apache2/sites-available/${sitename}" overwrite="true">
    <filterchain>            
      <replaceregexp>
        <regexp pattern="####" replace="${sitename}" ignoreCase="true" />
      </replaceregexp>
    </filterchain>
  </copy>

  <!-- update hosts file -->
  <php expression="chr(10)" returnProperty="LF"/>
  <append destFile="/etc/hosts" text="${LF}127.0.0.1  www.${sitename}.local" />

  <!-- apply site and reload apache configs -->
  <exec command="a2ensite ${sitename}" />
  <exec command="apache2ctl -k restart" />
</target>

I have added comments in so that it is clear what is going on, but one thing that might not be immediately clear is the following:

<php expression="chr(10)" returnProperty="LF"/>
<append destFile="/etc/hosts" text="${LF}127.0.0.1  www.${sitename}.local" />

If you are new to Phing you might not be sure what is going on here, but what we doing is using PHP to assign a variable with a new line character (created by calling chr(10)). We then use that variable to append a line of text to the hosts file, making sure that it gets printed on a line of it's own.

In order for this to work (due to the permissions of some of the files) you will need to run the build file as root, like this.

sudo phing -f build.xml

This has been tested on the latest version of Ubuntu (11.10) so your mileage may vary depending on the system you are running.

Running this on Windows basically means some different folders, as well some different commands. One thing that I had to change was the vhosts setting in the Apache httpd.conf file. By default on Windows this points to a single file as in "conf/vhosts.con". Just create a directory called vhosts in the conf directory and change this setting to "conf/vhosts/*". This will pick up everything in that directory so all we need to do is add a new file for each virtual host.

After some trial and error I realized the best way of creating the vhost entries was to separate the vhost templates into a Linux and a Windows version. This could be done through variables, but this way is slightly easier to maintain.

<VirtualHost *:80>
	ServerName ####.local
	ServerAlias www.####.local	
	ServerAdmin [email protected]

	DocumentRoot "C:/var/srv/####/public_html/"
	<Directory />
		Options FollowSymLinks
		AllowOverride All
	</Directory>
	<Directory "C:/var/srv/####/public_html/">
		Options Indexes FollowSymLinks MultiViews
		AllowOverride All
		Order allow,deny
		allow from all
	</Directory>

	ErrorLog logs/####_error.log

	# Possible values include: debug, info, notice, warn, error, crit,
	# alert, emerg.
	LogLevel warn

	CustomLog logs/####_access.log combined
</VirtualHost>

With this in place we can now create the Windows target.

<target name="createwindowssite">
  <!-- Target for creating a windows virtual host -->
  <echo>Creating www.${sitename}.local</echo>
  <!-- create site directory -->
  <mkdir dir="C:\var\srv\${sitename}\public_html" />

  <!-- write vhosts file with the information -->
  <copy file="windowsvhost.conf" tofile="C:\Apache Software Foundation\Apache2.2\conf\vhosts\${sitename}.conf" overwrite="true">
    <filterchain>            
      <replaceregexp>
        <regexp pattern="####" replace="${sitename}" ignoreCase="true" />
      </replaceregexp>
    </filterchain>      
  </copy>

  <!-- update hosts file -->
  <php expression="chr(10)" returnProperty="LF"/>
  <append destFile="C:\WINDOWS\system32\drivers\etc\hosts" text="${LF}127.0.0.1  www.${sitename}.local" />

  <!-- restart apache -->
  <exec command="httpd -k restart" />
</target>

Putting all this together we are left with the following:

<?xml version="1.0"?>
<project name="control" default="main">
  <target name="main">
    <propertyprompt propertyName="sitename" defaultValue="" promptText="Enter site name" /> 
    <if>
      <equals arg1="${sitename}" arg2="" casesensitive="false" />
      <then>
        <echo>No site entered, quiting.</echo>
      </then>
      <else>
        <if>
          <equals arg1="${host.os}" arg2="Linux" casesensitive="false" />
          <then>
            <phingcall target="createlinuxsite" />
          </then>
          <elseif>
            <equals arg1="${host.os}" arg2="WINNT" casesensitive="false" />
            <then>
              <phingcall target="createwindowssite" /> 
            </then>
          </elseif>
          <else>
            <echo>Host type "${host.os}" not found.</echo> 
          </else>
        </if>
      </else>
    </if>
    <echo>Done!</echo>
  </target>

  <target name="createlinuxsite">
    <!-- Target for creating a linux virtual host -->
    <echo>Creating www.${sitename}.local</echo>
    <!-- create site directory -->
    <mkdir dir="/var/www/${sitename}/public_html" />

    <!-- write vhosts file with the information -->
    <copy file="linuxvhost.conf" tofile="/etc/apache2/sites-available/${sitename}" overwrite="true">
      <filterchain>            
        <replaceregexp>
          <regexp pattern="####" replace="${sitename}" ignoreCase="true" />
        </replaceregexp>
      </filterchain>
    </copy>

    <!-- update hosts file -->
    <php expression="chr(10)" returnProperty="LF"/>
    <append destFile="/etc/hosts" text="${LF}127.0.0.1  www.${sitename}.local" />

    <!-- apply site and reload apache configs -->
    <exec command="a2ensite ${sitename}" />
    <exec command="apache2ctl -k restart" />
  </target>

  <target name="createwindowssite">
    <!-- Target for creating a windows virtual host -->
    <echo>Creating www.${sitename}.local</echo>
    <!-- create site directory -->
    <mkdir dir="C:\var\srv\${sitename}\public_html" />

    <!-- write vhosts file with the information -->
    <copy file="windowsvhost.conf" tofile="C:\Apache Software Foundation\Apache2.2\conf\vhosts\${sitename}.conf" overwrite="true">
      <filterchain>            
        <replaceregexp>
          <regexp pattern="####" replace="${sitename}" ignoreCase="true" />
        </replaceregexp>
      </filterchain>      
    </copy>

    <!-- update hosts file -->
    <php expression="chr(10)" returnProperty="LF"/>
    <append destFile="C:\WINDOWS\system32\drivers\etc\hosts" text="${LF}127.0.0.1  www.${sitename}.local" />

    <!-- restart apache -->
    <exec command="httpd -k restart" />
  </target>
</project>

Although this build file was created with local development in mind, I'm sure it can be adapted for creating virtual hosts on any Apache web server. It can also be expanded to include custom variables or virtual host setups but this build file is ideal for getting things in place and working quickly. Any custom details can be added separately.

Comments

Thanks for this, just what I was looking for!
Permalink
Glad I could help :)
Name
Philip Norton
Permalink
Please do :) I was thinking of adding a 'phing recipes' section to the site because I so often find myself coming back here to copy and paste stuff. Good idea with the github repo though!
Name
Philip Norton
Permalink
I just realized you have more phing recipes posted on your blog. So if you'd like to collaborate, you could also submit a pull request with some stuff! The guys at the phing project are ready to integrate this into their docs, if it gains some traction.
Permalink

Add new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
4 + 5 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.