Update iptables on Endian Community Firewall (EFW) 2.4.0

Compiling ip6tables on Endian Community Firewall (EFW) 2.4.0

Unfortunately the version of ip6tables available at the time of fedora core 3 doesn’t support the ‘state’ or ‘comment’ modules for use with firewall rules. So in order to get these, I decided to compile iptables for Endian.

To do this, we’ll need a build environment on the Endian box, we’ll also install wget.

cd /root
rpm -Uvh --nodeps http://archives.fedoraproject.org/pub/archive/fedora/linux/core/3/i386/os/Fedora/RPMS/wget-1.9.1-17.i386.rpm
wget http://sourceforge.net/projects/efw/files/Development/EFW-2.4-RESPIN/EFW-COMMUNITY-2.4-devel-srpms.tar.gz/download -O EFW-COMMUNITY-2.4-devel-srpms.tar.gz
tar -xvf EFW-COMMUNITY-2.4-devel-srpms.tar.gz
cd EFW-COMMUNITY-2.4-201006071652/RPMS/
rpm -Uvh gcc-* binutils-* cpp-* glibc-extras-* glibc-*headers-* glibc-devel-* libgomp-* libstdc++-devel-* make-* rpm-build-* patch-*

Now, we can compile iptables.

So firstly, lets download and install the sources we will need:

wget http://download.fedora.redhat.com/pub/fedora/linux/releases/16/Fedora/source/SRPMS/iptables-1.4.12-2.fc16.src.rpm
mkdir -p /usr/src/endian/{SOURCES,BUILD,RPMS}
wget http://www.linuximq.net/patchs/iptables-1.4.12-IMQ-test4.diff -O /usr/src/endian/SOURCES/iptables-1.4.12-IMQ-test4.diff
rpm --nomd5 -i iptables-1.4.12-2.fc16.src.rpm

And modify the spec file to make it compile on Endian:

egrep -vi "(SOURCE[12]|ip6?tables-config|ip6?tables.init|ip6?tables.service)" /usr/src/endian/SPECS/iptables.spec > /usr/src/endian/SPECS/iptables.spec.temp
mv /usr/src/endian/SPECS/iptables.spec.temp /usr/src/endian/SPECS/iptables.spec
sed -i 's#CFLAGS=#export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed s/-mtune=generic//`\nCFLAGS=#g' /usr/src/endian/SPECS/iptables.spec
sed -i 's#rm -f include/linux/types.h##g' /usr/src/endian/SPECS/iptables.spec
sed -ri 's#^(Patch5:.*)$#\1\nPatch502: iptables-1.4.12-IMQ-test4.diff#g' /usr/src/endian/SPECS/iptables.spec
sed -ri 's#^(%patch5.*)$#\1\n%patch502 -p1#g' /usr/src/endian/SPECS/iptables.spec
rpmbuild --nodeps -bb /usr/src/endian/SPECS/iptables.spec

And then install it:

rpm --nodeps -Uvh /usr/src/endian/RPMS/i386/iptables-

now iptables and ip6tables will be version, and ip6tables will have the extra missing modules.

If anyone wants a copy of the generated RPM just leave a message here and I’ll get them uploaded somewhere.

IPv6 with Endian Community Firewall (EFW) 2.4.0

First post in over a year! Oops.

For a while now, my home ADSL provider (EntaNET) has provided me with an IPv6 allocation, but I’ve never really used it (Its been on my to-do list for some time) primarily due to the fact that it is unsupported by Endian which I use for my home router/firewall.

However the other day after being asked about IPv6 at my day job, I decided I wanted to get this working, and decided to document it here in case it can assist anyone else in future. (I also finally got round to completing the Hurricane Electric IPv6 Certification up to sage level)

There’s a few things worth noting before we continue here.

  1. I use a Draytek Vigor 120 for my adsl modem - this is a PPPoA to PPPoE bridge. This means that my Endian box uses PPPoE to get its Internet connection, and directly receives an IPv4 address via the PPP session. There is no “PPP Half-Bridge” tricks here (such as where Modem does authentication, then DHCPs the address to Endian).
  2. Due to Endian lacking support for IPv6 you will need to use SSH to configure this, and any Endian upgrades will probably reverse a fair chunk of it. (Also, some reconfigurations may also undo things) - so with this in mind the rest of this guide assumes you are familiar with SSH and have successfully logged in as root to the Endian box (SSH can be enabled under the “System” section and “SSH Access”).
  3. Due to previous requirements, my Endian server is not “pure” in that I have additional packages installed that made this easier. Notably, a complete build environment. This won’t be needed here.
  4. This was all done without writing it down, so this documentation is based on my recollection and attempts at replicating various parts on a VirtualBox VM (which can’t do PPPoE…). If I’ve missed anything, please let me know in the comments.
  5. This was done with EFW 2.4.0 and may not work in the latest 2.5.1 version.
  6. I have only had this running for a few days, so there may be some unforeseen issues with this.

With this in mind, we continue to the actual important stuff!

The way EntaNET do IPv6, with a default setup you will get an IP Address allocated over PPP that is in a /64, but you also get a /56 which is routed to you. We will use a /64 from the /56 as the address for the LAN.

For the purposes of this, we are going to assume the following:

  • 2001:DB8:4D51:AA00::/56 - /56 range allocated to us by the ISP
  • 2001:DB8:4D51:AAFF::/64 - /64 range we are going to use internally.
  • 2001:DB8:4D51:FFFF::/64 - /64 range advertised across the PPP session.

The first thing to do, is to have Endian actually ask for IPv6 from the upstream provider at PPP time. This is easy:

echo "+ipv6" >> /etc/ppp/peers/defaults/pppd-pppoe

Assuming that the ADSL provider and modem both support IPv6, and you have been assigned an allocation you will see an IPv6 address attached to ppp0 once your session is active. This is from the PPP /64 and is not part of your /56 allocation.

So now we know that IPv6 works we can disconnect the PPP session.

Now, we actually want to be able to do something with our allocation, so we will want to announce it to our network.

For this, we will need radvd which will send the required RA Packets out to the network. As Endian 2.4.0 is built on Fedora Core 3, we can use the existing package for this, these can currently be found here

Unfortunately, Endian doesn’t quite provide a complete environment, so we will need to force the install to ignore dependencies (specifically, chkconfig and /sbin/service are missing).

rpm --nodeps -Uvh http://archives.fedoraproject.org/pub/archive/fedora/linux/core/3/i386/os/Fedora/RPMS/radvd-0.7.2-9.i386.rpm

We can now configure this to announce our prefix by editing /etc/radvd.conf to something like this:

interface br0
        AdvSendAdvert on;
        MinRtrAdvInterval 3;
        MaxRtrAdvInterval 10;
        AdvHomeAgentFlag off;
        prefix 2001:DB8:4D51:AAFF::/64
                AdvOnLink on;
                AdvAutonomous on;
                AdvRouterAddr off;

To trick radvd into starting, we also need to create a dummy file that exists in real RedHat-esque distros that Endian doesn’t provide:

echo "NETWORKING_IPV6=yes" >> /etc/sysconfig/network

We also need to enable IPv6 forwarding:

sysctl net.ipv6.conf.all.forwarding=1

and we should now be able to start radvd:

/etc/init.d/radvd start

Now, if we bring the ppp connection back up, you’ll notice that the ppp0 interface no longer gets allocated a routable IPv6 address from the PPP /64. This is because with ipv6 forwarding turned on, this host is now acting as an ipv6 router, and ipv6 routers ignore RA packets.

This isn’t a problem.

At this point, your LAN boxes will have IPv6 addresses, but the LAN boxes won’t be able to communicate with the internet yet.

To fix this, we need to tell the Endian box how to route traffic, specifically to both our LAN, and the default route:

route --inet6 add 2001:DB8:4D51:AAFF::/64 dev br0
route --inet6 add default dev ppp0

With this however, the Endian box won’t have IPv6 connectivity, if this is something that is required, we can do something like this instead:

ip -6 addr add 2001:DB8:4D51:AAFF::/64 dev br0
route --inet6 add default dev ppp0

But remember, that any time Endian makes any changes to the network configuration, this will be lost.

Endian’s version of iputils is missing ping6 and traceroute6, but we can install these as follows:

cd /
curl http://archives.fedoraproject.org/pub/archive/fedora/linux/core/3/i386/os/Fedora/RPMS/iputils-20020927-16.i386.rpm > iputils.rpm
rpm2cpio iputils.rpm | cpio -ivd '*6'
rm iputils.rpm

This will give you ping6 etc to allow you to verify everything so far.

The next thing to do then is firewalling, this is done with ip6tables, which again Endian doesn’t have, however we can install ip6tables using the iptables-ipv6 package available in the RPM repo above)

rpm -Uvh http://archives.fedoraproject.org/pub/archive/fedora/linux/core/3/i386/os/Fedora/RPMS/iptables-ipv6-1.2.11-3.1.i386.rpm

Now you’ll be able to create firewall rules for your IPv6 connectivity. Its worth noting though that this version of ip6tables doesn’t support some modules (comment and state that I’ve seen so far). If you want these modules, then you’ll need to compile a newer version of iptables. (I’ve got a follow up post with a guide for this.)

To support this, I wrote a set of scripts for parsing “formatted-english” rules files into iptables rules, so lets install that and configure some rules.

cd /root
wget https://github.com/ShaneMcC/Firewall-Rules/zipball/master -O fwrules.zip
unzip fwrules.zip
mv ShaneMcC-Firewall-Rules-* fwrules
cp fwrules/example.rules fwrules/rules.rules
chmod a+x fwrules/run.sh

Looking at fwrules/rules.rules should give you a good guide on how the rules work, and you can edit these to your needs.

Once you are happy, the rules can be installed by running:


The last thing then is to make this all work automatically.

In theory we should be able to just drop some files into the subfolders of /etc/uplinksdaemon, or into ifup.d or ifdown.d folders inside mkdir /var/efw/uplinks/main/ but neither of these approaches works. So instead we will make a minor modification to /usr/lib/uplinks/generic/hookery.sh and then have a script of our own do it.

Firstly, the minor change and an empty file:

sed -ri 's#^(.*log_done "Notify uplinks.*)$#\1\n    /sbin/uplinkchanged.sh "$@" >/dev/null 2>&1#' /usr/lib/uplinks/generic/hookery.sh
touch /sbin/uplinkchanged.sh
chmod a+x /sbin/uplinkchanged.sh

Now we can put the following into /sbin/uplinkchanged.sh:




if [ "${STATUS}" = "" ]; then
	exit 0;


if [ "${UPLINK}" = "main" ]; then
	if [ "${STATUS}" = "OK" ]; then
		sysctl net.ipv6.conf.all.forwarding=1
		/etc/init.d/radvd start
		route --inet6 add ${OURPREFIX} dev br0 2>&1
		route --inet6 add default dev ppp0
	elif [ "${STATUS}" = "FAILED" ]; then
		route --inet6 del default dev ppp0

		ip6tables -F
		ip6tables -X
		ip6tables -P INPUT ACCEPT
		ip6tables -P OUTPUT ACCEPT
		ip6tables -P FORWARD ACCEPT

Now when the state of the main uplink changes, the relevant ipv6-related commands will be run to ensure that connectivity remains.

And that’s it, you should now have native IPv6 connectivity combined with Endian. Feel free to leave any comments you have regarding this.

A tale of two monitors

So, a while back (just under 3 years ago) I obtained 2 of Hyundai’s W240D monitors. These monitors had (I believe) PVA panels and worked fine for most of their life so far.

A while back they both developed a problem, as evident in the video below:

So, as they were still under warranty I contacted Hyundai/RepairTech and arranged for these to be repaired. Hyundai sent the parts to RepairTech, who collected the units, repaired them and sent them back.

And this was the result:

So quite obviously they have come back with different panels from each other - but also different panels than I sent them in with.

One of them now has a TN panel, and one of them now has a panel that flickers when being videoed.

I sent the TN panel back (as it was clearly no where near what I sent in) and after a while it came back - still with a TN panel, but without a working stand. I sent it back again, and after eventually getting Hyundai involved I now have it back with a PVA panel.

The problem I find now, is that the second one (the one I had considered to be fine after the initial repair) is clearly not using the same panel technology, after searching and discovering the service menu, it became obvious why.

As you can see one of them has an RTC control, for TN or IPS panels, as its not a TN panel we can assume that is is an IPS panel.

So now I have 2 monitors, which previously were able to show images identically, that now show everything (subtly) differently - which isn’t what I wanted. (I bought 2 at the same time from the same place in order to get them the same)

I’ve invested in a colour calibrator to try and bring them closer together, but I know I won’t be able to get them the same. The PVA monitor also has no ability to change the colour values on the monitor itself when using DVI and not in service mode (Video).

For most people, there wouldn’t be any problem with this - not everyone cares about panel technology, or that 2 monitors side by side are the same (not everyone even has 2 of the same model side-by-side anyway), but now I find myself with a dilemma, that needs to be resolved quickly. (The warranty period on the IPS monitor runs out on 2nd December)

  • I could just accept it and move on and let it annoy me. (How often do I have both monitors showing things that are similar enough for the differences to be obvious?)

  • I could contact RepairTech again and request that they swap out the one with an IPS Panel for one with a PVA Panel (Is it really acceptable during a repair to fundamentally change the product? And do I really want the whole hassle of dealing with RepairTech again?)

  • I could sell both of the monitors and buy 2 new different identical monitors (This of course means finding acceptable replacements which is a chore in itself)

Choices, Choices, Choices.

I think I’m going to contact RepairTech to try and get this sorted properly, I had PVA panels, I should have PVA panels, even if it means dealing with RepairTech again. I can then look at the option of replacing the 2 monitors in the future when they have to be replaced, rather than doing it just for the sake of getting them to be the same.

Greasemonkey script for hp.com forums

If you’ve ever visited the hp.com forums you’ll know that any links in the post get enclosed by a call to “javascript:openExternal(“)” in the href rather than doing it properly in onClick. Amongst other things, this breaks the ability to middle click to open links in new tabs.

This finally annoyed me enough today and as a result, I now use the following greasemonkey script:

// ==UserScript==
// @name           Stupid HP.COM Links
// @namespace      http://shanemcc.co.uk/
// @include        *hp.com*
// ==/UserScript==

var a = document.getElementsByTagName("A");
for (var i = 0; i < a.length; i++){
	var href = a[i].href;
	href = href.replace(/javascript:openExternal\('([^']+)'\)/i, '$1');

	a[i].href = href;

This will make the links no longer have the call to openExternal around them, and thus make them middle-click friendly.

Ubuntu on HP Compaq Mini 311c-1030SA

Post thumbnail

I recently purchased a HP Compaq Mini 311c-1030SA with Nvidia ION and built in 3G, unfortunately the 3G card is a “UN2400” which isn’t supported right out of the box as it requires proprietary firmware.

This post is mostly notes for myself on getting the UN2400 3G card inside it working enough to use.

This post assumes that the netbook is running Ubuntu maverick (which is currently in alpha but seems to work just fine) as it has gobi_loader as a package and a kernel which supports it.

When I installed Ubuntu on here, I kept the original windows partition in case it was needed. I’m glad I did otherwise this would have been more awkward as some files from the windows installation are needed to get this working.

Before doing anything, boot the windows partition and connect to 3G from the HP Connection manager (This generates some log files which we need). Also change the settings on the 3G card not to turn off on shutdown. (Not sure if the last bit is needed but I did it anyway from reading other posts online about this card.)

Now back in Ubuntu, lets make this work:

# Install gobi-loader
sudo apt-get install gobi-loader

# Create missing directory
sudo mkdir -p /lib/firmware/gobi

# Use full paths for gobi_loader
sudo sed -i 's@"gobi_loader@"/lib/udev/gobi_loader@' /lib/udev/rules.d/60-gobi.rules

# Mount windows partition
sudo mkdir /mnt/windows
sudo mount /dev/sda1 /mnt/windows

# The HP Connect software comes with some pppd and chat scripts already for us.
sudo cp "/mnt/windows/Documents and Settings/All Users/HP/HPCM/WWAN/ppprc" /etc/ppp/peers/hpcm
sudo cp "/mnt/windows/Documents and Settings/All Users/HP/HPCM/WWAN/chat" /etc/ppp/hpcm-chat

# Update file path and add us to the dip group
sudo sed -i 's@~/chat@/etc/ppp/hpcm-chat@' /etc/ppp/peers/hpcm
sudo usermod -a -G dip `id -nu`

# Find which images were push to the card so we can copy them
sudo iconv --from-code UTF-16 --to-code UTF-8 "/mnt/windows/Documents and Settings/All Users/Application Data/QUALCOMM/QDLService2k/QDLService2kHP.txt" | grep -i "sending image"

This produces something like:

08/04/2010 23:09:55.093 [01840] QDL sending image file: C:\Program Files\Qualcomm\Images\HP\UMTS\AMSS.mbn
08/04/2010 23:10:00.281 [01840] Sending image file: C:\Program Files\Qualcomm\Images\HP\UMTS\Apps.mbn
08/04/2010 23:10:02.046 [01840] Sending image file: C:\Program Files\Qualcomm\Images\HP\4\UQCN.mbn

Copy the images referenced in the log file:

sudo cp /mnt/windows/Program\ Files/QUALCOMM/Images/HP/UMTS/amss.mbn /lib/firmware/gobi/
sudo cp /mnt/windows/Program\ Files/QUALCOMM/Images/HP/UMTS/apps.mbn /lib/firmware/gobi/
sudo cp /mnt/windows/Program\ Files/QUALCOMM/Images/HP/4/UQCN.mbn /lib/firmware/gobi/

Next you’ll want to stop modemmanager corrupting the firmware whilst it is being uploaded by blacklisting the non-modem version of the device:

echo 'ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="241d", ENV{ID_MM_DEVICE_IGNORE}="1"' >> /lib/udev/rules.d/77-mm-usb-device-blacklist-custom.rules

Now restart the machine so that udev picks up the device and uploads the firmware to it.

Once restarted you should be able to connect to the mobile broadband like so:

pon hpcm

In maverick network-manager understands that this card is a mobile broadband card, so it should be possible to configure it rather than using the ppp/chat scripts from hpcm but using the scripts is a good way to test if it works, and they have in them the information required by network-manager.

I just stick with pon/poff however as NetworkManager on KDE doesn’t seem to even bother to try and connect (I’ve had success with nm-applet though so I might just use that). In KDE, I’ve found that KNetworkManager isn’t the best at using this device (or my work VPN), so I use nm-applet to configure and use this device unless I don’t have a working X session (a common occurrence when using beta versions of Ubuntu!).

Now, for the ION chip.

Firstly install the Nvidia driver from jockey (also install the Broadcom STA driver for wireless) and restart.


apt-get install vlc libva1 vdpau-va-driver nvidia-185-libvdpau pkg-config
shutdown -r now

Now this should be all that’s needed, but it doesn’t appear to work right now, VLC video playback of h264 is still un-watchable, I shall keep trying.

Update: Using the final-release version of Maverick I have got this working in VLC. Open VLC, go to Tools > Preferences and go to “Input & Codecs” and enable “GPU Acceleration”.

Update 2: Unfortunately, the final release version of Maverick has This Bug which stops the 3G card working all the time.

Update 3: After updating this netbook to natty, the 3G bug still exists. Unfortunately I have yet to get round to git-bisecting against the kernel, as I don’t fancy compiling multiple kernels on a netbook!

Update 4: As a result of the bug report I submitted about this bug, I was directed to another bug report which described the same problem on a slightly different Qualcomm based device. (Modem requires firmware to be loaded and swaps device IDs before/after, modem works 1 in 20 times, etc). The alternative bug report suggests that modemmanager is actually at fault rather than the kernel.

As such, the following solution does fix the problem:

echo 'ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="241d", ENV{ID_MM_DEVICE_IGNORE}="1"' >> /lib/udev/rules.d/77-mm-usb-device-blacklist-custom.rules

followed by restarting. (In theory, killing modemmanager, loading the firmware and restarting modemmanager will also work)

modemmanager now no longer corrupts the firmware as it is being uploaded and the device is fully usable. I’m still wondering what changed between the kernel 2.6.35-14 and 2.6.35-17 versions in maverick that caused this to stop working, but I’m glad to finally have a solution to this.

I have updated the post to include this step in the process of getting this working.

I did originally get the 3g card working on lucid which was a bit more awkward (it involved recompiling some drivers to allow gobi_loader) but I switched to maverick as it was easier (and also I wanted VLC support for the ION chip which has recently been added in maverick)

I used these instructions to make it work on lucid in the past.

Sending SMS with a Huawei E220

Today I decided to play with an old Huawei E220 I have lying around.

After getting it setup and recognised in Linux by following the first 5 steps from http://ubuntuforums.org/showthread.php?p=3656717

After this, restarting udev (restart udev) and replugging the device makes it ready to use.

Part of the testing I was doing, was to send text-messages using the device (as a way of sending status messages out-of-band if an internet connection isn’t available.) and threw together this quick script that relies on expect and kermit:

#!/usr/bin/expect -f
# Copyright (c) 2010 Shane Mc Cormack
# This script is used to send text messages using
# any AT-Compatible SMS-Capable serial device.

if {[llength $argv] < 3} {
	puts "Usage: $argv0 '<device>' '<number>' '<message>'";
	puts "<number> should be in international format.";
	exit 1;

set device [lindex $argv 0]
set number [lindex $argv 1]
set message [lindex $argv 2]

set escape "\x1C";

set timeout 25
match_max 100000

puts "Spawning: /usr/bin/kermit -b 9600 -8 -l ${device} -C "set exit warning off,set carrier-watch off,connect,exit""
spawn /usr/bin/kermit -b 9600 -8 -l ${device} -C "set exit warning off,set carrier-watch off,connect,exit"

expect -- "----------------------------------------------------" { send "AT+CMGF=1\r\n" }
expect -- "OK" { send "AT+CMGS="${number}"\r" }
expect -- ">" { send "${message}\032" }

expect -- "OK" {
	puts "";
	send "${escape}";
	send "c"

Usage is simple:

[09:51:22] [shane@ShanePc:~/3gsms]$ ./sendSMS.sh /dev/ttyUSB0 "+447XXXXXXXXX" "Test message from CLI"
Spawning: /usr/bin/kermit -b 9600 -8 -l /dev/ttyUSB0 -C "set exit warning off,set carrier-watch off,connect,exit"
spawn /usr/bin/kermit -b 9600 -8 -l /dev/ttyUSB0 -C set exit warning off,set carrier-watch off,connect,exit
Connecting to /dev/ttyUSB0, speed 9600
 Escape character: Ctrl-\ (ASCII 28, FS): enabled
Type the escape character followed by C to get back,
or followed by ? to see other options.
> Test message from CLI

+CMGS: 11


[09:51:31] [shane@ShanePc:~/3gsms]$



I have recently decided to switch this site to use wordpress rather than the custom code that was here before.

This will allow me to edit/post to the site using my phone, which will probably allow me to update it more often.

I am working on migrating everything from the old site to the new site and trying to find a theme I like. Hopefully I will have everything working soon. Should anything be missing that you would like brought back leave a comment and let me know and I’ll see what I can do.

Unfortunately for anyone reading this using RSS, I’m afraid the recent posts will duplicate themselves as the IDs in the RSS feed will have changed.

Ident Server

I recently encountered a problem on a server that I manage where by the oidentd server didn’t seem to be working.

Manual tests worked, but connecting to IRC Servers didn’t.

I tried switching oidentd with ident2 and the same problem.

After switching back, and a bit of debugging later it appeared that the problem was that the IRC Servers were expecting spaces in the ident reply, whereas oidentd wasn’t giving them.

I then quickly threw together an xinet.d-powered ident server with support for spoofing.

First the xinet.d config:

service ident
	disable = no
	socket_type = stream
	protocol = tcp
	wait = no
	user = root
	server = /root/identServer.php
	nice = 10

Unfortunately yes, this does need to run as root otherwise it is unable to see what process is listening on a socket. In future I plan to change it to allow it to run without needing to be root (by using sudo for the netstat part)

Now for the code itself:

Edit: The code is now available on github: ShaneMcC/phpident

I welcome any comments about this, or any improvements and hope that it will be useful for someone else.

GitWeb Hacking.

Recently I setup gitweb on one of my servers to allow a web-based frontend to any git projects which the users of the server place in their ~/git/ directory.

After playing about with it, I noticed that it allowed for placing a README.html file in the git config directory to allow extra info to be shown on the summary view, managed to get it to pull the README.html file from the actual repository itself, and not the config directory, thus allowing the README.html to be versioned along with everything else, and not require the user to edit it on the server, but rather just edit it locally and push it.

This is a simple change in /usr/lib/cgi-bin/gitweb.cgi:

From (line 3916 or so):

	if (-s "$projectroot/$project/README.html") {
		if (open my $fd, "$projectroot/$project/README.html") {
			print "<div class=\"title\">readme</div>\n" .
			      "<div class=\"readme\">\n";
			print $_ while (<$fd>);
			print "\n</div>\n"; # class="readme"
			close $fd;


if (my $readme_base = $hash_base || git_get_head_hash($project)) {
		if (my $readme_hash = git_get_hash_by_path($readme_base, "README.html", "blob")) {
			if (open my $fd, "-|", git_cmd(), "cat-file", "blob", $readme_hash) {
				print "<div class=\"title\">readme</div>\n";
				print "<div class=\"readme\">\n";

				print <$fd>;
				close $fd;
				print "\n</div>\n";

I also added a second slightly hack that uses google’s code prettyfier when displaying a file, and makes the line numbers separate from the code so they don’t copy also when you copy the code,

From (line 2476 or so):

print "</head>\n" .


print qq(<link href="http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.css" type="text/css" rel="stylesheet" />\n);
        print qq(<script src="http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.js" type="text/javascript"></script>\n);

        print "</head>\n" .
              "<body onload=\"prettyPrint()\">\n";


From (line 4351 or so):

while (my $line = <$fd>) {
		chomp $line;
		$line = untabify($line);
		printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
		       $nr, $nr, $nr, esc_html($line, -nbsp=>1);


print "<table><tr><td class=\"numbers\"><pre>";
	while (my $line = <$fd>) {
		chomp $line;
		printf "<a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a>\n", $nr, $nr, $nr;
	print "</pre></td>";
	open my $fd2, "-|", git_cmd(), "cat-file", "blob", $hash;
	print "<td class=\"lines\"><pre class=\"prettyprints\">";
	while (my $line = <$fd2>) {
		chomp $line;
		$line = untabify($line);
		printf "%s\n", esc_html($line, -nbsp=>1)
	print "</pre></td></tr></table>";
	close $fd2;

This could do with a quick clean up (reuse $fd rather than opening $fd2) but it works.