Building PXE Imager from Scratch

This document describes how you can recreate PXE Imager from the beginning. This serves both as documentation for myself, and as a guide for others who wish to create a stripped down version of Debian. PXE Imager is a tool that boots a system over the network, then presents a menu of preconfigured disk images to be installed, and then installs them. It is composed of a stripped down Debian disk image and a Python program that does the dirty work. To create it, you need root access to a Linux system. The first step is to create a Debian root filesystem. The debootstrap tool in Debian is used to do this, with the following command.

debootstrap sarge rootdir [Mirror URL]

This will download all the files in the release sarge into rootdir. However, nothing is configured. We will need to fix that. First, create the /etc/fstab file. The following works for PXE booting, since it uses a RAM disk as root.

# /etc/fstab: static file system information.
# <file system>	<mount point>	<type>	<options>	<dump>	<pass>

/dev/ram0	/		ext2	defaults	0	0
proc		/proc		proc	defaults	0	1
tmpfs		/tmp		tmpfs	defaults	0	1

Next, we need to configure the locale for the keyboard with the following command. I selected "keep kernel keymap." You will get a bunch of errors, since it won't work in the chroot, but it should work when you boot. Also, you probably want to configure debconf to use "readline" for configuring packages, so that it works on a serial console.

dpkg-reconfigure console-data
dpkg-reconfigure debconf

Network Configuration

First, create a /etc/hosts file (easy way: cp /etc/hosts rootdir/etc/hosts). Since we are using PXE boot, we know that the system has an Ethernet card. Assuming that it is found during the boot process, use the following for /etc/network/interfaces, as it will automatically bring up eth0 using DHCP.

# Used by ifup(8) and ifdown(8). See the interfaces(5) manpage or
# /usr/share/doc/ifupdown/examples for more information.

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 inet dhcp

Configure your hostname:

echo pxeboot > rootdir/etc/hostname

Edit /etc/inittab to run bash instead of presenting a login prompt. This allows us to remove a bunch of packages related to logging in, which won't be needed anyway.

#2:23:respawn:/sbin/getty 38400 tty2
#3:23:respawn:/sbin/getty 38400 tty3
#4:23:respawn:/sbin/getty 38400 tty4
#5:23:respawn:/sbin/getty 38400 tty5
#6:23:respawn:/sbin/getty 38400 tty6

Remove Useless Packages

Since we want to put this image in RAM, we need to remove all the useless stuff. To remove packages, run chroot rootdir dpkg --purge [package names]. Here is a list that I removed:

ppp pppconfig pppoe pppoeconf whiptail libnewt0.51 libpcap0.7
logrotate libpopt0 at sysklogd klogd
iptables ipchains wget telnet
cron exim4 exim4-base exim4-config exim4-daemon-light mailx
base-config adduser makedev
apt apt-utils base-config aptitude tasksel 
nano nvi ed
man-db manpages groff-base info
pciutils bsdmainutils fdutils cpio modutils
console-tools console-common console-data libconsole

# --force-depends needed to remove the following:
tcpd libwrap0 netkit-inetd iputils-ping

# Finally, libraries
libsigc++-1.2-5c102 libtextwrap1 liblockfile1
libssl0.9.7 libdb4.2 libpcre3 libgnutls11
libtasn1-2 liblzo1 libopencdk8
libgcrypt11 libgpg-error0

# Need --force-depends, or to install debconf-english
libtext-wrapi18n-perl libgdbm3 libtext-iconv-perl
liblocale-gettext-perl libtext-charwidth-perl

Then install the following packages:

# replaces debconf-i18n, which is much larger

# Small vi clone

# Automatic hardware detection
discover discover-data libdiscover2 libexpat1

# Needed for 2.6 kernel modules

# For TFTP transfers
atftp libncurses5

# For the "" script (use --force-depends to install)

/dev Devices

Modern Linux distributions use tools like udev to create devices in /dev as needed. Those tools are great, but take up too much space for our stripped down image. I just created them the old fashioned way. You can use the MAKEDEV script to create devices for you, but I find it creates far too many. I simply used the /dev directory from Pebble Linux.


Since this image is going to be booted over the network, the kernel configuration is a bit unconventional. The only files we need on the actual system are the modules, since the kernel itself is downloaded over the network. I unpack the kernel package, and then manually install the modules. Also, you will need the kernel on your TFTP server (/boot/vmlinuz-*), so be sure to copy it to your /tftpboot directory.

dpkg-deb -x [kernel image] [temp dir]
mv [temp dir]/lib/modules/* rootdir/lib/modules
cp [temp dir]/boot/vmlinuz-* /tftpboot

At this point, we have a basic usable Debian system. To make life easier, I like to package up the root at this point as rootdir.tar.

To present a nice menu at boot, copy the script to rootdir. You will then need to modify the /etc/inittab file to start it on the first console:

Stripping Down the Image

Now we need to remove a bunch of stuff in order to save disk space, to make this actually bootable over the network. My approach is to remove a bunch of Debian packages which are considered "essential" but we won't use, and then manually delete a bunch of files. To make this easier, I've created a script called that does all the deleting. It is included in the PXE Imager source distribution.

Creating a Disk Image

You can either use ext2 or CramFS. CramFS makes a slightly larger image, but it is much faster to boot since it does not need to be decompressed in memory. However, its filesystems are read-only. Since it is easier to deal with a writeable root, I use ext2. The script includes the following commands for creating the disk image:

dd if=/dev/zero of=pxeimager.img bs=1k count=32000
mke2fs -F pxeimager.img
tune2fs -c 0 -i 0 pxeimager.img
mount pxeimager.img /mnt -t ext2 -o loop
cp -ar rootdir.shrink/* /mnt
umount /mnt
gzip pxeimager.img