Diskless Raspberry Pi

Maybe this needs some explanation: I use a word that could mean "to make something diskless" and "to make something naked" in the original Hungarian title. Sadly, this does not work as well in English.

Usually, SD cards aren't the most reliable parts of a Raspberry Pi-based system. You can find many horror stories on the internet about failing data storage and data loss. However, my luck lasted a long time, I ran a lot of systems 24/7, and I didn't have a single SD card failure.

Nothing lasts forever, as the saying goes. The troubles started when I tried to smarten my TV with a LibreELEC system running on Raspberry Pi 4. I don't know exactly the difference between this system and all the others before it, but it killed an SD card, and I had to reinstall the system on another one because it couldn't boot.

So it was a pleasure to read that Model 4 already supports network booting, which could lead to an SD card-less world. But, as it always happens, it wasn't a smooth ride.

Upgrading the hardware

There is a good chance that our Pi is running an older bootloader without the proper support for network booting, so as strange as it sounds, we will need an SD card to go diskless.

No matter what system we want to run in the end to this SD card, we should install the Raspberry Pi OS (previously known as Raspbian). After the successful installation and boot, we need to extract the last eight characters of the serial number, which we will need later.

$ grep Serial /proc/cpuinfo
Serial    : 10000000xxxxxxxx

Then a little bit of system upgrade to get the latest bootloader which we will apply right away, and finally a reboot, so our changes take effect.

# apt-get update
# apt-get upgrade
# rpi-eeprom-update
# reboot

Last but not least, we need to modify the config to include network booting in the boot order. Just hit the Boot Options > Boot Order > Network Boot menu, reboot the Pi one last time (hopefully), and we can finally get rid of the SD card as well.

# raspi-config

Of course, at first, I tried to boot the system without this config change for hours, and I couldn't figure out why it wanted to boot from the nonexisting SD card.

The server

We will need another machine on the network to team up with for network booting. Part of the communication will be handled by DHCP, but we will need TFTP and NFS (Need For Speed?) server as well.

I'm not gonna lie; I'm pretty sure that I don't fully understand this whole thing. Still, in my head, I imagine something like this: DHCP is part of the discovery phase so the machine knows that someone supports network booting, the first few files come through TFTP until the machine is able to boot up to a point where it can download the rest of the files through NFS.

So, first things first, we need a DHCP and a TFTP server. Fortunately, dnsmasq can handle both. I was in that comfortable situation that I already had a dnsmasq server running as a DHCP server. Of course, if you want a separate DHCP server to assign IP addresses for the machines on the network, that's totally doable too.

# apt install dnsmasq

We need a directory that TFTP can use to serve the files. The xxxxxxxx in the path is the last eight character of the serial that we noted down earlier.

# mkdir -p /var/lib/tftpboot/xxxxxxxx

Now we need to configure TFTP in dnsmasq.

/etc/dnsmasq.conf
enable-tftp
tftp-root=/var/lib/tftpboot
pxe-service=0,"Raspberry Pi Boot"

After a quick systemctl restart dnsmasq.service we can continue with the installation of the NFS server.

# apt install nfs-common nfs-kernel-server

A couple of more directories to serve the files from.

# mkdir -p /nfs/libreelec/{boot,storage}

And we connect the boot directory served by NFS with the serial directory served by TFTP, so the contents will be the same.

/etc/fstab
/nfs/libreelec/boot /var/lib/tftpboot/xxxxxxxx none defaults,bind 0 0

Lastly, we have to tell the NFS that if someone from a specific IP address asks for these files, it should happily serve them. If the machine has no fixed IP address, we could use an IP range or a * as well.

/etc/exports
/nfs/libreelec/boot <raspberry ip>/32(ro,sync,no_root_squash,no_subtree_check)
/nfs/libreelec/storage <raspberry ip>/32(rw,sync,no_root_squash,no_subtree_check)

If we don't believe in the beneficial nature of firewalls, then we can stop right here. Otherwise, we have to tell mountd (whatever it is) what port it should use so we can poke a hole in the firewall for it.

/etc/default/nfs-kernel-server
RPCMOUNTDOPTS="--manage-gids -p 13025"

Servers are rumbling; nothing left to do but fill the directories with the operating system files. At this point, I had a working SD card, and I didn't want to lose my settings, so I mounted the filesystems from it and copied the files to the new NFS directories.

# fdisk -l
[...]

Disk /dev/sdb: 7.4 GiB, 7948206080 bytes, 15523840 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xb774a380

Device     Boot   Start      End  Sectors  Size Id Type
/dev/sdb1  *       8192  1056767  1048576  512M  c W95 FAT32 (LBA)
/dev/sdb2       1056768 15523839 14467072  6.9G 83 Linux

# mkdir /media/{boot,storage}
# mount /dev/sdb1 /media/boot
# mount /dev/sdb2 /media/storage
# cp -a /media/boot /nfs/libreelec/boot
# cp -a /media/storage /nfs/libreelec/storage
# umount /media/boot
# umount /media/storage
# rmdir /media/{boot,storage}

Copying the files from a disk image could also work. We don't have to care about the storage part this time, we should leave it empty, and LibreELEC will fill it with files during the first boot. However, mounting the image is a bit more cumbersome. We need to multiply the starting sector of the filesystem with the sector size (it's 8192 * 512 for the example below) to get the proper offset.

# fdisk -l LibreELEC-RPi4.arm-9.2.5.img
Disk LibreELEC-RPi4.arm-9.2.5.img: 549 MiB, 575668224 bytes, 1124352 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xcb770fc1

Device                        Boot   Start     End Sectors  Size Id Type
LibreELEC-RPi4.arm-9.2.5.img1 *       8192 1056767 1048576  512M  c W95 FAT32 (LBA)
LibreELEC-RPi4.arm-9.2.5.img2      1056768 1122303   65536   32M 83 Linux

# mount -o loop,offset=<sector size * start> LibreELEC-RPi4.arm-9.2.5.img /media/boot

The files are in place, but we still have to tell the operating system where it should look for them.

/nfs/libreelec/boot/cmdline.txt
ip=dhcp boot=NFS=<szerver ip>:/nfs/libreelec/boot disk=NFS=<szerver ip>:/nfs/libreelec/storage

At this point, the only remaining thing that could cause us some headaches is the firewall settings. TFTP needs UDP port 69, TCP port 111, and NFS needs ports 2049 and 13025 (or the port we previously set). Powering up the Raspberry Pi now (without an SD card) will hopefully result in the marvelous experience of proper booting.

Powering up the Raspberry Pi now (without an SD card) will hopefully result in the marvelous experience of proper booting.

Further reading

Ez a bejegyzés magyar nyelven is elérhető: A Raspberry Pi lemeztelenítése

Have a comment?

Send an email to the blog at deadlime dot hu address, or visit the tweet related to this post.

Want to subscribe?

We have a good old fashioned RSS feed if you're into that, and you can also follow the blog on Twitter.