Rescuing a Broken pfSense Install

They don’t make flash drives like they used to. I’ve seen several pfSense installs fail recently due to drives flaking out and the wear-leveling not working as advertised.

Of course, you make regular backups of your config file, but in case you forgot, we can probably rescue your config file off of a disk image. Re-installing pfSense doesn’t take too long, but rebuilding a working config file can take many hours, so rescuing is preferential.

This script has worked for me with dozens of file versions, but one can imagine scenarios with fragmented files where it would fail. There’s nothing fancy going on here (this was hacked up with a client standing in my office with a broken pfSense box), but it might prove useful in a pinch.

#!/usr/bin/perl -w
use strict;
use warnings FATAL=>'all';

=comment
pfsense_extract.pl - extract pfSense configs from an input stream
(c) 2010 BFC Computing, LLC.  Licensed under the same terms as pfSense.

This is useful for taking an image file of a damaged pfSense install
and pulling out config files.  If you can mount the image normally, you
should do that first.

Due to the nature of the filesystem, there are often many copies of a
config file in a disk image, from each time it was saved.  You will
find a bunch of output files named: pfsense-config-1.xml, pfsense-config-2.xml,
etc.  You can then use tools like diff to find out which the right one was.

Example:
   dd if=/dev/sdg of=broken_pfsense_image.dd bs=2M conv=sync,noerror
   strings broken_pfsense_image.dd | perl pfsense_extract.pl

Processing a 1GB image as per the example takes about 20 seconds on a standard
2GHz desktop machine.
=cut

my $BASENAME='pfsense-config-X.xml';
my $counter = 0;
my ($outfile);
my $do_output = 0;

while () {

    chomp;

    if ($_ eq '<pfsense>') {
	$counter++;
	my $filename = $BASENAME;
	$filename =~ s/X/$counter/;
	open($outfile,">$filename");
	$do_output = 1;
    }

    if ($do_output) {
	print $outfile $_ . "n";
    }

    if ($_ eq '</pfsense>') {
	close $outfile;
	$do_output = 0;
    }

}