# Tutorial

nobodd is a confusingly named, but simple TFTP server intended for net-booting Raspberry Pis directly from OS images without having to loop-back mount or otherwise re-write those images.

In order to get started you will need the following pre-requisites:

- A Raspberry Pi you wish to netboot. This tutorial will be assuming a Pi 4, but the Pi 2B, 3B, 3B+, 4B, and 5 all support netboot. However, all have subtly different means of configuring their netboot support, so in the interests of brevity this tutorial will only cover the method for the Pi 4.

- A micro-SD card. This is only required for the initial netboot configuration of the Pi 4, and for discovering the serial number of the board.

- A server that will serve the OS image to be netbooted. This can be another Raspberry Pi, but if you eventually wish to scale to several netbooting clients you probably want something with a lot more I/O bandwidth. We will assume this server is running Ubuntu 24.04, and you have root authority to install new packages.

- Ethernet networking connecting the two machines; netboot will not operate over WiFi.

- The addressing details of your ethernet network, specifically the network address and mask (e.g. 192.168.1.0/24).

## Client side

To configure your Pi 4 for netboot, use rpi-imager to flash Ubuntu Server 24.04 64-bit to your micro-SD card. Boot your Pi 4 with the micro-SD card and wait for cloud-init to finish the initial user configuration. Log in with the default user (username “ubuntu”, password “ubuntu”, unless you specified otherwise in rpi-imager), and follow the prompts to set a new password.

Run sudo rpi-eeprom-config --edit, and enter your password for “sudo”. You will find yourself in an editor, with the Pi’s boot configuration from the EEPROM, which will most likely look something like the following:

    [all]
    BOOT_UART=0
    WAKE_ON_GPIO=1
    ENABLE_SELF_UPDATE=1
    BOOT_ORDER=0xf41

Note

Do not be concerned if several other values appear, or the ordering differs. Various versions of the Raspberry Pi boot EEPROM have had differing defaults for their configuration, and some later ones include a lot more values.

The value we are concerned with is `BOOT_ORDER` under the `[all]` section, which may be the only section in the file. This is a hexadecimal value (indicated by the “0x” prefix) in which each digit specifies another boot source in reverse order. The digits that may be specified include:

# | Mode | Description
--- | --- | ---
1 | SD CARD | Boot from the SD card
2 | NETWORK | Boot from TFTP over ethernet
4 | USB-MSD | Boot from a USB MSD
e | STOP | Stop the boot and display an error pattern
f | RESTART | Restart the boot from the first mode

A full listing of valid digits can be found in the Raspberry Pi documentation. The current setting shown above is “0xf41”. Remembering that this is in reversed order, we can interpret this as “try the SD card first (1), then try a USB mass storage device (4), then restart the sequence if neither worked (f)”.

We’d like to try network booting first, so we need to add the value 2 to the end, giving us: “0xf412”. Change the “BOOT_ORDER” value to this, save and exit the editor.

Warning

You may be tempted to remove values from the boot order to avoid delay (e.g. testing for the presence of an SD card). However, you are strongly advised to leave the value 1 (SD card booting) somewhere in your boot order to permit recovery from an SD card (or future re-configuration).

Upon exiting, the rpi-eeprom-config command should prompt you that you need to reboot in order to flash the new configuration onto the boot EEPROM. Enter sudo reboot to do so, and let the boot complete fully.

Once you are back at a login prompt, log back in with your username and password, and then run sudo rpi-eeprom-config once more to query the boot configuration and make sure your change has taken effect. It should output something like:

    [all]
    BOOT_UART=0
    WAKE_ON_GPIO=1
    ENABLE_SELF_UPDATE=1
    BOOT_ORDER=0xf412

Finally, we need the serial number of your Raspberry Pi. This can be found with the following command.

    $ grep ^Serial /proc/cpuinfo
    Serial          : 10000000abcd1234

Note this number down somewhere safe as we’ll need it for the server configuration later. The Raspberry Pi side of the configuration is now complete, and we can move on to configuring our netboot server.

## Server side

As mentioned in the pre-requisites, we will assume the server is running Ubuntu 24.04, and that you are logged in with a user that has root authority (via “sudo”). Firstly, install the packages which will provide our TFTP, NBD, and DHCP proxy servers, along with some tooling to customize images.

    $ sudo apt install nobodd-tftpd nobodd-tools nbd-server xz-utils dnsmasq

The first thing to do is configure dnsmasq(8) as a DHCP proxy server. Find the interface name of your server’s primary ethernet interface (the one that will talk to the same network as the Raspberry Pi) within the output of the ip addr show up command. It will probably look something like “enp2s0f0”.

    $ ip addr show
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
        inet6 ::1/128 scope host
            valid_lft forever preferred_lft forever
    2: enp2s0f0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
        link/ether 0a:0b:0c:0d:0e:0f brd ff:ff:ff:ff:ff:ff
        inet 192.168.1.4/16 brd 192.168.1.255 scope global enp2s0f0
           valid_lft forever preferred_lft forever
        inet6 fd00:abcd:1234::4/128 scope global noprefixroute
           valid_lft forever preferred_lft 53017sec
        inet6 fe80::beef:face:d00d:1234/64 scope link
            valid_lft forever preferred_lft forever
    3: enp1s0f1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP group default qlen 1000
        link/ether 1a:0b:0c:0d:0e:0f brd ff:ff:ff:ff:ff:ff
    4: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
        link/ether 02:6c:fc:6f:56:5c brd ff:ff:ff:ff:ff:ff
        inet6 fe80::60d9:48ff:fee3:c955/64 scope link
           valid_lft forever preferred_lft forever
    ...

Create the file `/etc/dnsmasq.d/nbd.conf` and add the following lines to it, adjusting the ethernet interface name, and the network mask on the highlighted lines to your particular setup.

    # Only listen on the primary ethernet interface
    interface=enp2s0f0
    bind-interfaces

    # Perform DHCP proxying on the network, and advertise our
    # PXE-ish boot service
    dhcp-range=192.168.1.255,proxy
    pxe-service=0,"Raspberry Pi Boot"

Restart dnsmasq to ensure it’s listening for DHCP connections.

    $ sudo systemctl restart dnsmasq.service

Next, we need to obtain an image to boot on our Raspberry Pi. We’ll be using the Ubuntu 24.04 Server for Raspberry Pi image as this is configured for NBD boot out of the box. We will place this image under a `/srv/images` directory and unpack it so we can manipulate it.

    $ sudo mkdir /srv/images
    $ sudo chown ubuntu:ubuntu /srv/images
    $ cd /srv/images
    $ wget http://cdimage.ubuntu.com/releases/24.04/release/ubuntu-24.04-preinstalled-server-arm64+raspi.img.xz
     ...
    $ wget http://cdimage.ubuntu.com/releases/24.04/release/SHA256SUMS
     ...
    $ sha256sum --check --ignore-missing SHA256SUMS
    $ rm SHA256SUMS
    $ unxz ubuntu-24.04-preinstalled-server-arm64+raspi.img.xz

We’ll use the nobodd-prep command to adjust the image so that the kernel will try and find its root on our NBD server. At the same time, we’ll have the utility generate the appropriate configurations for nbd-server(1) and nobodd-tftpd.

nobodd-prep needs to know several things in order to operate, but tries to use sensible defaults where it can:

- The filename of the image to customize; we’ll simply provide this on the command line.

- The size we want to expand the image to; this will be size of the “disk” (or “SD card”) that the Raspberry Pi sees. The default is 16GB, which is fine for our purposes here.

- The number of the boot partition within the image; the default is the first FAT partition, which is fine in this case.

- The name of the file containing the kernel command line on the boot partition; the default is `cmdline.txt` which is correct for the Ubuntu images.

- The number of the root partition within the image; the default is the first non-FAT partition, which is also fine here.

- The host-name of the server; the default is the output of hostname --fqdn but this can be specified manually with `nobodd-prep --nbd-host`.

- The name of the NBD share; the default is the stem of the image filename (the filename without its extensions) which in this case would be `ubuntu-24.04-preinstalled-server-arm64+raspi`. That’s a bit of a mouthful so we’ll override it with `nobodd-prep --nbd-name`.

- The serial number of the Raspberry Pi; there is no default for this, so we’ll provide it with `nobodd-prep --serial`.

- The path to write the two configuration files we want to produce; we’ll specify these manually with `nobodd-prep --tftpd-conf` and `nobodd-prep --nbd-conf`

Putting all this together we run,

    $ nobodd-prep --nbd-name ubuntu-noble --serial 10000000abcd1234 \
    > --tftpd-conf tftpd-noble.conf --nbd-conf nbd-noble.conf \
    > ubuntu-24.04-preinstalled-server-arm64+raspi.img

Now we need to move the generated configuration files to their correct locations and ensure they’re owned by root (so unprivileged users cannot modify them), ensure the modified image is owned by the “nbd” user (so the NBD service can read and write to it), and reload the configuration in the relevant services.

    $ sudo chown nbd:nbd ubuntu-24.04-preinstalled-server-arm64+raspi.img
    $ sudo chown root:root tftpd-noble.conf nbd-noble.conf
    $ sudo mv tftpd-noble.conf /etc/nobodd/conf.d/
    $ sudo mv nbd-noble.conf /etc/nbd-server/conf.d/
    $ sudo systemctl reload nobodd-tftpd.service
    $ sudo systemctl reload nbd-server.service

## Testing and troubleshooting

At this point your configuration should be ready to test. Ensure there is no SD card in the slot, and power it on. After a short delay you should see the “rainbow” boot screen appear. This will be followed by an uncharacteristically long delay on that screen. The reason is that your Pi is transferring the initramfs over TFTP which is not the most efficient protocol [1]. However, eventually you should be greeted by the typical Linux kernel log scrolling by, and reach a typical booted state the same as you would with a freshly flashed SD card.

If you hit any snags here, the following things are worth checking:

- Pay attention to any errors shown on the Pi’s bootloader screen. In particular, you should be able to see the Pi obtaining an IP address via DHCP and various TFTP request attempts.

- Run `journalctl -f --unit nobodd-tftpd.service` on your server to follow the TFTP log output. Again, if things are working, you should be seeing several TFTP requests here. If you see nothing, double check the network mask is specified correctly in the dnsmasq(8) configuration, and that any firewall on your server is permitting inbound traffic to port 69 (the default TFTP port).

- You will see numerous “Early terminate” TFTP errors in the journal output. This is normal, and appears to be how the Pi’s bootloader operates [2].

[1]

absent certain extensions, which the Pi’s bootloader doesn’t implement.

[2]

at a guess it’s attempting to determine the size of a file with the `tsize` extension, terminating the transfer, allocating RAM for the file, then starting the transfer again. While not strictly necessary, remember that the bootloader operates with limited resources and simplicity of operation is the order of the day.