Lighttpd and Debian Jessie on your Beaglebone Black


Updated: 2014-12-13

The Beaglebone Black is a neat, compact ARM board centered around a TI SoC. With the word ARM almost synonymous to the Raspberry Pi nowadays, here's how the Beaglebone stacks up against the Pi:

  • 1 GHz ARM v7 CPU vs. 700 MHz ARM v6
  • 512 MB DDR3L vs. 512 MB LPDDR2
  • Ethernet SoC support vs. USB Ethernet
  • 2 GB eMMC (onboard flash) vs. none

As you can see, the Beaglebone Black one-ups the Pi on quite a few points. Especially the Ethernet port is a nice improvement, since the USB Ethernet on the Pi could tax the CPU quite a bit during intensive network traffic. So while the Beaglebone does not have the graphic prowess the Pi has, it is a better workhorse all-round. The onboard flash is pretty convenient, it's big enough for a small footprint linux installation, and it should also be quicker than a microSD card.

After an unsuccessful try-out as an MPD server (the HDMI out is pretty wonky, and my receiver would stop seeing it after a while, be it cold or warm boot of the Beaglebone, no difference), I intended to sell this board but realised it would make a pretty power efficient and convenient always-on web server. So I dusted off the Beaglebone, installed a fresh Debian, and moved my blog and downloads off the x86_64 server.

Installing Debian and upgrading to Jessie

Up to revision B (which is the model I have), Beaglebone Blacks shipped with the Angström Distribution. From revision C on it was replaced by Debian. There are two types of images available for the Beaglebone: the ones that you just boot from a microSD card, and the ones that will, upon booting from that card, write themselves to the eMMC. Since I'm not too familiar with flash layouts (they don't use the pretty sdX names x86_64 systems do), I prefer the latter. I pulled mine from the Embedded Linux Wiki. The flasher images they offer are all based on Wheezy (the stable release at present), and come with a 3.8 kernel. Surprisingly, though, they do use systemd, which Debian will only switch to with the next stable release, Jessie. So grab the flasher image and write it to a microSD card:

# xzcat BBB-eMMC-flasher-debian-7.5-console-armhf-2014-07-06-2gb.img | dd bs=2M of=/dev/sdX

Next, insert the microSD card and hold down the USER/BOOT button, then connect power. First, two blue LEDs will turn on, then you'll see them start looping all four (this means the eMMC is being written to). When the looping stops and all LEDs burn steadily, you may disconnect the power and reboot the board.

Like the Angström distribution, this Debian image will set up a tethered USB network interface with IP 192.168.7.2, in addition to a traditional eth0 interface. You can login as the user debian with password temppwd. Then, we switch from Wheezy to Jessie:

# sed -i 's|wheezy|jessie|g' /etc/apt/sources.list
# aptitude update && aptitude dist-upgrade

Reducing installation footprint

The image installed is not stripped down, so it's wise to inspect the package list prior to updating, and remove everything you do not need. That should also trim down the size of the upgrades to be pulled in. Deborphan is also a handy tool to remove any leftover packages. Besides that, you can perform some tweaks to aptitude and dpkg. Here, we tell apt not do install additional packages.

# echo 'APT::Install-Recommends "0" ; APT::Install-Suggests "0" ;' > /etc/apt/apt.conf.d/01footprint

Next, we tell dpkg not to install any locales except English and Dutch. Redefine this as needed.

# cat /etc/dpkg/dpkg.cfg.d/excludes 
path-exclude=/usr/share/locale/*
path-include=/usr/share/locale/en/*
path-include=/usr/share/locale/nl/*
path-include=/usr/share/locale/nl_BE/*
path-include=/usr/share/locale/locale.alias

path-exclude=/usr/share/man/*
path-include=/usr/share/man/man[1-9]/*
path-include=/usr/share/man/en/*
path-include=/usr/share/man/nl/*
path-include=/usr/share/man/nl_BE/*

An updated version of these instructions is kept in this article.

The dpkg tweaks only take effect upon upgrades or installation of new packages. Either you reinstall everything (I do not recommend this if you wiped your package cache already) or you manually rm -rf the directories in question. This can save multiple tens of MiB, on a 2 GB system this can help quite a bit. If you're looking for more ways to slim down your installation, take a look at the Debian Wiki.

Updating the kernel

The Beaglebone has a custom kernel that does not seem to be updated beyond 3.8. However, Robert Nelson has been providing newer kernels on a regular basis. People have been using his update scripts (which can be downloaded at the same spot) for a while, but they have been discontinued for a reason which is not clear to me. You're prompted to use the script in /opt/scripts/tools/, which keeps telling me the latest is 3.8.

At least as of 3.16, the kernel package will install the DTBs - Device Tree Blobs - as well, and even update the bootloader configuration (uEnv.txt, so almost everything is taken care of. It never hurts to check though. The procedure outlined below is pre 3.16, you can skip this if you're upgrading to a later kernel.

So I have grabbed his last 3.14 kernel manually - 3.14.8-bone5.

# wget https://rcn-ee.net/deb/jessie-armhf/v3.14.8-bone5/linux-image-3.14.8-bone5_1.0jessie_armhf.deb
# wget https://rcn-ee.net/deb/jessie-armhf/v3.14.8-bone5/3.14.8-bone5-dtbs.tar.gz
# dpkg -i linux-image-3.14.8-bone5_1.0jessie_armhf.deb
# tar xf 3.14.8-bone5-dtbs.tar.gz

DTBs are databases that represent the hardware components on a given board. U-boot relies on the DTBs to pass hardware information on to the kernel. The tarball contains quite a lot of DTBs, but we only need a few (actually, we might need even less than the ones I extracted, but I'd rather have one too many than brick my device, this is not my cup of tea). I only extracted the AM335X ones. Keep in mind the directory they'll end up in needs to match the kernel name string of the kernel they're associated with (ie. uname -r output).

# mkdir /boot/dtbs/3.14.8-bone5/
cp am335x-*dtb /boot/dtbs/3.14.8-bone5/

After this, all that's left is adjusting the uEnv.txt file. All you need to change is the uname_r value, since the DTBs are already in the right place:

# vim /boot/uEnv.txt
uname_r=3.14.8-bone5

#uname_r=3.8.13-bone60
#dtb=
cmdline=quiet init=/lib/systemd/systemd

#enable bbb_flasher:
#cmdline=init=/opt/scripts/tools/eMMC/init-eMMC-flasher-v2.sh
mmcroot=UUID=f2exxxxx-xxxx-xxxx-xxxxxxxxxxxxxx881

Reboot, and if all went well, you can remove the 3.8 kernel(s).

Lighttpd, PHP and Pico

Installation itself is pretty straightforward:

# aptitude install lighttpd php5-cgi

Make sure to disable any module you do not need in Lighttpd. This is what I ended up with:

server.modules = (
    "mod_access",
    "mod_cgi",
    "mod_fastcgi",
    "mod_rewrite"
)

Just grab Pico from its homepage and untar into your webroot. I have noticed Pico takes 2 to 3 seconds to load any page, whereas phpinfo() and dir listings load instantly (like you expect on a modern system). There seem to be some hiccups in Twig and/or Pico, but this is uncharted territory for me, so I have been unable to trace it so far. In the meantime, I have enabled Twig's cache, but that barely changes anything, so I had to resort to the cache plugin for Pico. This works nicely, except of course the cache is outdated after a page gets modified, so I'm in limbo on how frequently I should regenerate the cache. The cron job would look like this:

# crontab -l
0 3 * * 3,7 rm /var/www/pico/content/cache/.html
0 3 * * 3,7 rm /var/www/pico/content/cache/*html
1 3 * * 3,7 /usr/bin/wget -mkp http://www.volatilesystems.org > /dev/null

As of October 2014 I've moved to Pelican, which is a fully static blogging framework, so no need for PHP anymore, just Lighttpd (and hopefully fewer possible security holes).

Security: chroot and iptables

I've put Lighttpd in a chroot, to separate it from the LAN services, and I've set up a basic firewall filtering incoming traffic. For now I decided not to block outgoing traffic, there are so few processes running I can see in the blink of an eye when something's amiss, and I'm pretty sure if someone hacks my system they can get to the firewall too. So here's how my iptables setup looks:

$ cat /etc/iptables.rules 
*filter

# Allow SSH traffic from LAN
-A INPUT -s 10.0.0.0/24 -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT

# Set default policies
-P INPUT DROP
-P FORWARD DROP
-P OUTPUT ACCEPT

# Accept loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
-A INPUT -d 127.0.0.0/8 ! -i lo -j REJECT

# Accept all established connections
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

# Allow HTTP(S) connections
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT

# Allow ping requests
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT

# Log denied connection attempts
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

COMMIT

I have added a script to /etc/network/if-pre-up.d/:

$ cat /etc/network/if-pre-up.d/iptables 
#!/bin/bash

/sbin/iptables-restore < /etc/iptables.rules

Make sure to make the file executable, or the script won't be parsed:

$ sudo chmod +x /etc/network/if-pre-up.d/iptables

This way, the firewall gets configured before the interfaces are brought up.