As promised on Twitter last week, here's Chris's article on getting Railo running on Rasperry Pi.
Over to Chris...
So you are a lucky owner of a sweet Raspberry Pi, you have a typical broadband connection, some ColdFusion language skills and you would like to take advantage of all these things and roll out a little Web site hosted at home, independently from any hosting provider, and make it available to your family, friends and the rest of the world? That's exactly what I've done and I thought I'd share a step-by-step guide on how my stack has been set up so you can give it a try, amend it and/or improve it.
The guide is inspired by an excellent article by Glyn Jackson on running Railo on Raspberry Pi that I found very useful when I started this exciting journey. However, the set-up described below differs slightly as the current version of Raspbian does not allow to install the official Oracle Java JDK and for that reason I'm going to opt for OpenJDK instead. And also, we don't need to worry about the SSH configuration as it's enabled by default in more recent versions of Raspbian.
Another deviation from Glyn's config is using a wired Internet connection rather than WiFi.
Prerequisites
- Standard broadband connection with a dynamically allocated IP address
If you are in the UK, it can be BT, Virgin or any other popular async broadband option (and if you are lucky enough to have an Internet connection with a static IP address, you might want to skip the section covering Dynamic DNS). - A router configurable to forward traffic on port 80 to a machine on the local network
I tested it with an Apple TimeCapsule and a BT HomeHub but other routers should provide that functionality as well. - Raspberry Pi (Model B) with 512MB of RAM
I haven't tested it on the 256 model but you might like to do it at your own risk. - An SD card with the Raspian GNU/Linux distribution installed on it
A step-by-step guide covering this topic can be found on the eLinux site. You will need direct or SSH access to the box with root permissions.
Setup summary
Possible alternatives | ||
---|---|---|
Hardware | Rasbperry Pi 512MB RAM | |
Operating System | GNU/Linux Raspbian | |
JRE | OpenJDK 1.6 | Oracle Java provided you are running a soft-float ABI distro (e.g. Debian) instead of Raspbian |
Web server | Lighttpd | Nginx |
Application server | Tomcat 7 | Jetty, Resin etc. |
CFML server | Railo 4 jars | Earlier version of Railo, Open BlueDragon (not confirmed). Adobe ColdFusion is unlikely to run on such limited hardware. |
Internet domain provider | DNS Dynamic | There is a number of alternative free and commercial services |
Power
One of the great things about Raspberry Pi is its low power consumption which means having it always on should not ruin your home budget. I'm no expert on the electricity stuff but I'm powering my Pi through a USB cable hooked to the Apple TimeCapsule which acts as our home router and is always on anyway so that means there are two devices plugged to one wall socket. Does this configuration mean a decreased power consumption? I honestly don't know but it's certainly nice to have one extra power socket available.In my case, the Pi is connected to TimeCapsule with two cables as the wired Internet connection is also coming from it (see the picture below).
Static IP on local network
OK, so now when our Pi is powered on and it's connected to the Internet, let's make sure its IP address on the local network is not dynamically assigned through DHCP but is static instead. This is to enable it to configure the router to forward Internet traffic to Raspberry Pi that we're going to focus on a little later.It's time to access the Raspberry Pi's shell and this can be done either via SSH or directly as long as you have a keyboard and a monitor attached to the board. If you're on the Mac or Linux, just fire up Terminal and type the commands below; Windows users will need to download a tool like PuTTY in order to interact with their Pi.
$ ssh [IP address of your RasberryPi]
Once you've logged in, open up /etc/network/interfaces in your favourite editor and make sure you have the following entries for the eth0 network interface:
$ sudo vi /etc/network/interfaces
…
iface eth0 inet static
address 192.168.1.7 # your RPi IP address goes here
netmask 255.255.255.0
gateway 192.168.1.1 # your router/Internet gateway IP
If the new static address is different from the one currently assigned to the Pi, you might be forced to restart the box for the changes to take effect:
$ sudo shutdown -r
Installing servers
Java and Tomcat
Since Raspbian is based on Debian, you get access to APT, the best Linux package manager in the world, out of the box. So let's get it to install all the necessary Java stuff for us: ChrissMachine:~ chris $ ssh 192.168.1.7
$ sudo apt-get --no-install-recommends install openjdk-7-jdk tomcat7 tomcat7-admin
The second command takes care of installing the OpenJDK Java stack and the Tomcat application server with its admin console. Please note we're also using the --no-install-recommends flag to ensure no optional software is installed.Interestingly enough, the version of Java installed after running the command above is 6, not 7. I never had a chance to get to the bottom of that but I suspect this is down to the Tomcat package dependencies that favour version 6 over 7.
Make sure the permissions are set properly by adding the following node to /etc/tomcat7/tomcat-users.xml:
<user username="system" password="[your password here]" roles="manager-gui,admin-gui"/>
Railo
Now when we have our Java stack in place, let's download the Railo CFML server. At the moment of writing this article, the current version of Railo is 4.0.2.002. $ mkdir ~/Downloads && cd ~/Downloads
$ wget -O railo-4.0.2.002-jars.tar.gz http://www.getrailo.org/down.cfm?item=/railo/remote/download/4.0.2.002/custom/all/railo-4.0.2.002-jars.tar.gz&thankyou=true
You might need to hit enter once the wget download has been completed.
Uncompress Railo jars and move the whole directory to /usr/share/ which is where Tomcat libraries also sit: $ sudo tar xzf railo-4.0.2.002-jars.tar.gz -C /usr/share/
Now, this is a very important part: you need to make sure the jar files are owned by the same process as Tomcat is run by, in my case the tomcat7 user and group: $ sudo chown -R tomcat7:tomcat7 /usr/share/railo-4.0.2.002-jars
For performance reasons we are going to serve static content via a Web server and only get Tomcat to handle ColdFusion files and hence we also need Lighttpd. Lighttpd is my favourite lightweight alternative to the famous Apache server that seems to be a perfect choice when you have limited hardware resources as is the case with Raspberry Pi. $ sudo apt-get install lighttpd
Configuring servers
Railo and Tomcat configuration
What we are going to do here is make Railo libraries available to Tomcat, add the Railo servlet to it and make sure .cfm and .cfc files are handled properly.Append the following paths to the common.loader property in /etc/tomcat7/catalina.properties in your favourite editor.
/usr/share/railo-4.0.2.002-jars,/usr/share/railo-4.0.2.002-jars/*.jar
A word of warning here: this is a global configuration change meaning Railo will be loaded on all of the Tomcat sites. Please refer to the Railo documentation if you'd like to be able to run Railo on a per-site basis.
Edit /etc/tomcat7/web.xml and add the following nodes inside the web-app element (uncomment the RestServlet bit if you want to use Railo 4 Rest services):
<servlet>
<servlet-name>CFMLServlet</servlet-name>
<servlet-class>railo.loader.servlet.CFMLServlet</servlet-class>
<init-param>
<param-name>configuration</param-name>
<param-value>{web-root-directory}/WEB-INF/railo/</param-value>
<description>Configuration directory</description>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CFMLServlet</servlet-name>
<url-pattern>*.cfm</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>CFMLServlet</servlet-name>
<url-pattern>*.cfml</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>CFMLServlet</servlet-name>
<url-pattern>*.cfc</url-pattern>
</servlet-mapping>
<!--
<servlet id="RestServlet">
<servlet-name>RestServlet</servlet-name>
<servlet-class>railo.loader.servlet.RestServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>RestServlet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
-->
And now instruct Tomcat to treat index.cfm as a default file by adding the following node to the welcome-file-list container in the same web.xml file:
<welcome-file>index.cfm</welcome-file>
Web site configuration
OK, it's time to decide on the domain for your new site. For the sake of this tutorial, say it's going to be mysite.dnsd.info but be careful here as you want to pick one that's actually available. Other domains offered by DNS Dynamic include dnsd.me, dnsdynamic.com, dnsdynamic.net, dnsget.org, user32.com and x64.me. No, they are not quite as neat as a simple .net or .co.uk but you still have quite a few to choose from.What you need to do now is work out what your current public IP is using a site like http://whatismyip.com/, register a free account on http://dnsdynamic.org/ and set up your domain pointing at that IP address.
Assuming the sites will be located under /var/www, let's create a directory where the site is going to sit and knock together a sample index.cfm file:
$ sudo mkdir -p /var/www/mysite.dnsd.info/html
$ sudo chown -R tomcat7:tomcat7 /var/www/mysite.dnsd.info
$ sudo chmod 2775 /var/www/mysite.dnsd.info/html
$ touch /var/www/mysite.dnsd.info/html/index.cfm # You might need to sudo here, depending on whether you belong to the tomcat7 group or not
The chmod command above will make sure any file created within the html dir will automatically belong to the tomcat7 group and will be fully writable by the owner and members of this group. If you believe this is not safe enough, feel free to customise the permissions.
Why don't you edit index.cfm and slap a “Hello World”, <cfoutput>#Now()#</cfoutput>, <cfdump var=”#CGI#”> or something equally useful onto it so we can prove in a moment CFML is actually parsed?
$ echo "<cfoutput>#Now()#</cfoutput>" > index.cfm
As we are going to finish the domain setup a bit later, let's add it to our hosts file so we can see if Railo is happy without having to remember the box's IP address. On the Mac or Linux the file you're after is /etc/hosts and on Windows you should be able to find it under C:/Windows/System32/drivers/etc/hosts. The entry that needs to be added to this file is this:
192.168.1.7 mysite.dnsd.info
Please note, this will need to be removed once the DNS Dynamic setup has been completed though.
Tomcat hostname
Next, we are going to register our new site in Tomcat. Open up /etc/tomcat7/server.xml and paste the code below within the <engine/> node: <Host name="mysite.dnsd.info" appBase="/var/www/mysite.dnsd.info">
<Context path="" docBase="html/" allowLinking="true" />
<Alias>www.mysite.dnsd.info</Alias>
</Host>
At this stage Railo should be able to serve the new site so let's go ahead and ensure the new hostname has been picked up by our application server:
$ sudo service tomcat7 restart
Now, there's a caveat here: this operation might take minutes to complete on Raspberry Pi (perhaps something to bear in mind if you need to restart Tomcat in the future). When it's in progress, it's worth monitoring the java process memory consumption with tools like the Unix top. Typically, it takes more than 90% of CPU until all Java classes are compiled and Tomcat is ready.
Once the top tool shows the Java process has got quiet, you should be able to access the test site from your machine where the hosts file has been modified. Just open up a browser and go to http://mysite.dnsd.info:8080/
Lighttpd configuration
Lighttpd stores its basic configuration details in /etc/lighttpd/lighttpd.conf. What we need to do here is register the .cfm index file and make sure the Web root is correct.First of all, let's open up the config file in your favourite text editor and take a look at the server.document-root setting and ensure the path specified there points to your Web root, in my case /var/www (without trailing slash). Lastly, add index.cfm to the index-file.names list, preferably after index.html.
What we need now is add support for multiple host names. Let's enable the Simple VHost module and configure it:
$ sudo lighty-enable-mod simple-vhost
What this does is it creates a 10-simple-vhost.conf symbolic link under /etc/lighttpd/conf-enabled. We now need to edit this file and make sure Lighttpd looks at the same Web root as Tomcat does which in our case is /var/www.
Edit this file and make sure you have the following options in there:
simple-vhost.server-root = "/var/www"
simple-vhost.document-root = "html"
You might also want to set the default host:
simple-vhost.default-host = "mysite.dnsd.info"
… though this can be any site, not necessarily the one we're currently setting up. We're done here so let's go ahead and reload the Web server configuration:
$ sudo service lighttpd force-reload
VoilĂ ! We have reached another milestone and we should now be able to access any non-ColdFusion resource on our site on port 80. What we have at this stage is both the Lighttpd Web server and the Tomcat application server handling our mysite.dnsd.info hostname and looking for files under the same location.
Lighttpd doesn't know what .cfm files are but we can try and see if it serves .html files correctly. Let's drop a sample test.html file under /var/www/mysite.dnsd.info/html, put a Hello-world in there, restart Lighttpd and fire it up in a Web browser. When you hit http://mysite.dnsd.info/test.html in Chrome, Firefox or any other browser, you should now see your hello world page.
Connecting Lighttpd with Tomcat
Now that we have both servers up and running, let's make sure all static files like .html, .js, .css and others are handled by Lighttpd leaving only the heavy CFML processing to Tomcat and Railo. This is basically an equivalent of the mod_jk module used in the Apache world.For this to work we need to enable the proxy module in Lighttpd:
$ sudo lighty-enable-mod proxy
Next, let's configure our proxy to only call Tomcat when non-static files are requested. Put the configuration below in /etc/lighttpd/conf-enabled/10-proxy.conf:
$HTTP["host"] =~ "^(www\.)?mysite\.dnsd\.info$" {
$HTTP["url"] !~ "\.(js|css|gif|jpe?g|png|ico|txt|swf|html|htm)$" {
proxy.server = (
"" => (
"tomcat" => (
"host" => "127.0.0.1",
"port" => 8080,
"fix-redirects" => 1
)
)
)
}
}
If you reload Lighttpd now you should be able to access your sample CFML template on port 80. Try browsing to http://mysite.dnsd.info/ or http://mysite.dnsd.info/index.cfm and you should see the Railo hello world page you've created before.
You should also be able to access the Railo administrator consoles under http://mysite.dnsd.info/railo-context/admin/ without the need to specify the 8080 Tomcat port number in the URL anymore. Do remember we are building a public-facing machine here so this is a good moment to set some hard-to-guess passwords for the Railo web and server admin consoles.
Persistent domain
And that leaves us with the last outstanding piece of the puzzle which is making sure your new site is accessible from the outside world on a 24/7 basis. As your broadband provider allocates the IP dynamically and it gets changed every now and then, we need to get our Raspberry Pi to:- be able to notify the DNS Dynamic service with the current IP address so it can keep its hostname-IP mappings up-to-date letting people access your sites even when your home IP gets changed,
- periodically monitor its public IP address for changes using cron and utilise the above DNS notifier,
- get the router to redirect the Web traffic to Raspberry Pi.
DNS Dynamic Web service
Let's keep things clean and tidy and store all our DNS Dynamic domains in one place. Create a file called /etc/dnsdynamic.org.lst and put all our domains in there, one per line. Save it and create another file with a simple Bash script capable of calling the DNS Dynamic REST api: $ sudo touch /usr/local/bin/update_dnsd
$ sudo chmod 0700 !$ # Make sure only root can access it to keep the password safe
Put the script below in there:
#!/bin/bash
DNSD_USERNAME=[your DNS Dynamic email user name]
DNSD_PASS=[your DNS Dynamic password]
DNSD_SERVER=www.dnsdynamic.org
for hostname in `cat /etc/dnsdynamic.org.lst`
do
curl -s -u $DNSD_USERNAME:$DNSD_PASS https://$DNSD_SERVER/api/?hostname=$hostname
sleep 3
done
exit 0
If you don't have the curl package installed you will need to get APT to give you a hand with this. If you take a closer look at the Web service URL invoked using curl, you'll notice you don't even have to worry about providing your current IP as DNS Dynamic works it out automatically based on the origin of your request.
Cron job
Firstly, create a file for caching our current public IP and also an executable script for notifying DNS Dynamic: $ sudo touch /etc/current_ip
$ sudo touch /usr/local/bin/ip_change_notify
$ sudo chmod 0755 /usr/local/bin/ip_change_notify
Copy and paste the following code in the ip_change_notify file:
#!/bin/bash
# Where do we want to cache the current external IP address?
ip_cache=/etc/current_ip
# What IP address checker Web Service do we use? Please note, it should only return the IP address in plain text.
ip_web_service_url=ifconfig.me
if [ ! -f $ip_cache ]; then
echo "INFO: Creating the $ip_cache file"
touch $ip_cache
fi
# What is my current external IP address?
current_ip=`curl -s $ip_web_service_url`
# Is it different from the one we saved last time?
previous_ip=`tail -n 1 $ip_cache`
if [ "$current_ip" = "$previous_ip" ]; then
echo "INFO: Your IP hasn't changed since last time"
exit 0
fi
if [ "$current_ip" = "" ]; then
echo "INFO: New IP empty, skipping"
exit 0
fi
# Update our dnsdynamic.org host entries
if [ -x /usr/local/bin/update_dnsd ]; then
/usr/local/bin/update_dnsd > /dev/zero 2>&1 &
fi
if [ ! -w $ip_cache ]; then
echo "WARNING: IP has changed by could not cache it as file is not writable"
exit 1
fi
# We are still here so this means we need to save the new IP address. Just make sure we substring() the IP.
echo ${current_ip:0:15} > $ip_cache
exit 0
In this example we are relying on ifconfig.me but you might as well opt for other services like ipecho.net or icanhazip.com.
OK, the task is ready so let's schedule it to run every now and then:
$ sudo crontab -u root -e
You will see the cron tasks editor and all you need to do is add a line similar to the one below and save the file:
14 7,9,13,15,17,22 * * * /usr/local/bin/ip_change_notify &
This particular one will run only six times during the day so you will most likely want to increase the frequency of this scheduled task. Refer to the cron documentation if you're not familiar with crontab`s syntax.
Port forwarding
And that leaves us with the last task before we can wrap the whole thing up. What you basically need to do is instruct your router to forward all Internet traffic on port 80 to the local network IP of the Raspberry Pi box on the same port number. Be careful though as this is where things are getting a bit dangerous as your newly set up Web server is hereby going live! You are therefore responsible for making sure the box is secure and well protected from any malicious stuff hiding in the depths of the Internet.For detailed instructions on how to configure port forwarding you might need to refer to your router's documentation. But if you happen to use TimeCapsule, all you need to do is fire up AirPort Utility, select your device and click Edit, then go to the Network tab and hit the plus icon under the Port Settings section. In the pop up window you then need to enter the “Private IP Address” that is the IP of your Raspberry Pi (in our case it's 192.168.1.7) and set public and private TCP ports to 80.
Just to confirm, I also set it all up using the admin interface provided by the BT Home Hub (if you are based in the UK, you probably heard about it) in the past and steps required to configure it were pretty much the same.
And last but not least, go back to your hosts file and comment out the domain entry that we hard-coded before as it's no longer needed.
This is it, your new CFML site is now live - just don't forget to let the world know what the URL is!
Oh, and just to prove all the stuff described above is real, here's a sample #Now()# site running off my home server: http://railotest.dnsd.info/ :-)
Chris Kobrzak
PS Many thanks to Adam for kindly agreeing to host this guest blog post.