PXE Booting Raspberry Pi OS: Part 2 – Server Setup


In this post I’m going to talk about booting raspios from your server – Network Booting, or PXE booting as it’s often called.

PXE booting (pronounced “pixie booting”) uses two common protocols – the Dynamic Host Configuration Protocol (DHCP) and the Trivial File Transfer Protocol (TFTP). As we’ll see below, setting these up on a Debian-based server is relatively straightforward. Roughly, the process is:

  • download the raspios image you want to use;
  • mount and copy it to an NFS share;
  • create a TFTP mount point; and
  • configure dnsmasq to point to it.

Setting up the client (a Raspberry Pi) for PXE booting is the subject of a different blog post. For now, the below will help you set up the server.

Note in all code below, a ‘\’ indicates a line continuation only and is done here for presentation purposes, ie. when typing out the code in a terminal you don’t actually type the ‘\’ but, instead, type the multple lines separated by the ‘\’ as a single command.

Install the tools you’ll need

We’re going to need unzip to decompress the .zipped raspios image file we’ll download from the raspberrypi.org website. We’ll need kpartx to help mount that (unzipped) .img file to some directories we’ll create – so that we can copy them across to our nfs share. nfs-kernel-server is needed to serve the nfs share that will contain the operating system(s) that will be available to the client(s). dnsmasq will be the magic that answers broadcast requests from our clients trying to pxeboot.

$ cd ~
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install unzip kpartx \
nfs-kernel-server dnsmasq

Download and copy your OS to an NFS share

Download a raspios image and unzip it and mount the .img file.

$ wget https://downloads.raspberrypi.org \
/raspios_armhf/images/raspios_armhf-2020-08-24 \
/2020-08-20-raspios-buster-armhf.zip
$ unzip 2020-08-24-raspios-buster-armhf.zip

Now use kpartx to create device nodes for each of the two partitions contained in the unzipped .img.

$ sudo kpartx -a 2020-08-24-raspios-buster-armhf.img

Create a new directory and mount the second partition (ie. the one containing all of the Raspberry Pi OS except the boot partition).

$ mkdir rootmnt
$ sudo mount /dev/mapper/loop0p2 rootmnt/

Create a directory for use as an NFS share and copy all of the OS files into it.

$ sudo mkdir -p /nfs/rpi
$ sudo cp -a rootmnt/* /nfs/rpi/

Now repeat this with the first partition (ie. the boot partition).

$ mkdir bootmnt
$ sudo mount /dev/mapper/loop0p1 bootmnt/
$ sudo mkdir /nfs/rpi/boot
$ sudo cp -a bootmnt/* /nfs/rpi/boot

Now unmount the .img as you no longer need it.

$ sudo umount rootmnt
$ sudo umount bootmnt
$ sudo kpartx -d 2020-08-24-raspios-buster-armhf.img

Now we need to do a few file manipulations.

First, we don’t need or want the booted OS to mount any disks so we remove the default mount points in the OS:

$ sudo sed -i /UUID/d /nfs/rpi/etc/fstab

Next, we create the ssh file to ensure OpenSSH is available once booted This is optional. If you don’t foresee the need to ssh into your client, then you can skip this step. If you are going headless, however, you’ll want to do it:

$ sudo touch /nfs/rpi/boot/ssh

Then, set the kernel parameters to be passed in at boot so that the kernel mounts the remote NFS share as the file system:

$ echo "console=serial0,115200 console=tty root=/dev/nfs \
nfsroot=192.168.1.{my ip}:/nfs/rpi,vers=3 rw rootwait \
ip=dhcp elevator=deadline" \
| sudo tee nfs/rpi/boot/cmdline.txt

Finally, finish setting up the nfs share:

$ echo "/nfs/rpi *(rw,sync,no_subtree_check,no_root_squash)"\
| sudo tee -a /etc/exports
$ sudo systemctl enable rpcbind
$ sudo systemctl enable nfs-kernel-server
$ sudo systemctl restart rpcbind
$ sudo systemctl restart nfs-kernel-server

Create the TFTP mount point

Now that our OS is setup on the nfs share, we need to create and edit the network boot infrastructure. Note that, below, I’m assuming the serial number of the client is deadbeef. Make sure you subsitute your client machine’s serial number where you see any reference to deadbeef:

$ sudo mkdir -p /tftp/deadbeef
$ echo "/nfs/rpi/boot /tftp/deadbeef none defaults,bind \
0 0" | sudo tee -a /etc/fstab
$ sudo mount /tftp/deadbeef
$ sudo chmod 777 /tftp

Now that we’ve set up the tftp directory and mounted our /nfs/rpi install to it, we need to configure dnsmasq to serve it up when requested by the client (at boot).

Configure dnsmasq

Set up dnsmasq:

$ echo "dhcp-range=192.168.1.255,proxy" | sudo tee \
-a /etc/dnsmasq.conf
$ echo "log-dhcp" | sudo tee -a /etc/dnsmasq.conf
$ echo "enable-tftp" | sudo tee -a /etc/dnsmasq.conf
$ echo "tftp-root=/tftp" | sudo tee \
-a /etc/dnsmasq.conf
$ echo "pxe-service=0,'Raspberry Pi Boot'" | sudo tee \
-a /etc/dnsmasq.conf
$ sudo systemctl enable dnsmasq
$ sudo systemctl restart dnsmasq

To test it all works, we start watching the server’s logs:

$ sudo tail -f /var/log/daemon.log

and now start the client. Any errors that cause a failure of the client to boot, should show up here. If you’ve followed the above, there shouldn’t be any.

Further reading

Categories:Linux

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: