Network Booting a Raspberry Pi 3 from an Ubuntu Server

Introduction

I often use Raspberry Pis as Chromium Kiosk Clients to monitor our systems and infrastructure, and to reduce the overheads I also Network Boot them for this and a few other uses (hopefully coming soon) so that they are easy to manage collectively without needing to flash numerous SD cards, and additionally removing need to worry abou the life of SD cards after many write operations.

This article is initially based on the following guides, but looks to includes a bit more detail and troubleshooting steps:

It is also possible to Network Boot a Raspberry Pi 2.

Setup

The Server

A HP Micro Server (G7 N54L) running Ubuntu 16.04.3 LTS (doing various other things too)

The Client(s)

Raspberry Pi 3 Model B (connected via Wired Ethernet, this process does not work over WiFi)

The Target OS

Raspbian 9 Lite (non Lite should work too)

Preparing the Root Filesystem to Boot

In this set of steps we will copy a new Raspbian install, with a few tweaks to our boot server and expose it over an NFS share.

Step 1: Raspbian Lite SD Card

Download the Raspbian Lite Image from here.

Flash to a Micro SD card, the easiest way is to use Etcher (it will even take a zip directly and also verifies the content after).

Step 2: Boot the Raspberry Pi from the new SD Card

Note: This will need a screen, keyboard, and Ethernet cable (with internet access) connected.

Once the Pi boots login with the default credentials (pi / raspberry).

Step3: Enable SSH (Optional)

Run sudo raspi-config, selection Option 5 (Interfacing Options), P2 (SSH) and select Yes to Enable SSH.

You can then quit this utility and continue the following steps via SSH if preferred, or continue locally.

The IP is printed on boot but can also be viewed by running ip addr:

pi@raspberrypi:~ $ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    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: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether b8:27:eb:41:8e:fa brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.135/24 brd 192.168.1.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::2036:591:cef2:341a/64 scope link 
       valid_lft forever preferred_lft forever
3: wlan0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    link/ether b8:27:eb:14:db:af brd ff:ff:ff:ff:ff:ff

In this case our wired interface is eth0, which has the address 192.168.1.135.

Step 4: Update Debian

First, run sudo apt-get update to ensure all package index files are up-to-date.

Then, run sudo apt-get upgrade to install the newest versions of all packages currently installed.

Then, run sudo apt-get dist-upgrade which intelligently handles changing dependencies with new versions of packages.

In both cases when requested, check and confirm you are happy with the changes proposed.

Step 5: Disable Swap
sudo dphys-swapfile swapoff
sudo dphys-swapfile uninstall
sudo update-rc.d dphys-swapfile remove
Step 6: Update the Pi (firmware)

Certain files from the next branch of the rpi-firmware repository are required for this process to work.

This step is probably optional if bootcode.bin is used from GitHub in the TFTP steps below.

Edit: I've also observed using the 'stock' raspbian kernel img files can cause nasty boot errors.

Run sudo BRANCH=next rpi-update.

Edit (2018-07-14): Warning, the next branch appears to be out of date now, updating from master (the default) works fine for 3B+.

Details on rpi-update can be found here https://github.com/Hexxeh/rpi-update.

It is likely to complete by saying:

 *** A reboot is needed to activate the new firmware

So, reboot now with sudo reboot.

Step 7: 'Clone' the filesystem

Once the Pi has rebooted, locally or via SSH, run:

sudo mkdir -p /nfs/client1
sudo apt-get install -y rsync
sudo rsync -xa --progress --exclude /nfs / /nfs/client1

Note: You must do this on a running Pi, copying off the SDK card on another host did not appear to work.

Regenerate SSH host keys (arguably optional):

cd /nfs/client1
sudo mount --bind /dev dev
sudo mount --bind /sys sys
sudo mount --bind /proc proc
sudo chroot .
rm /etc/ssh/ssh_host_*
dpkg-reconfigure openssh-server
exit
sudo umount dev
sudo umount sys
sudo umount proc

Remove the duplicated swap file if it exists:

sudo rm /nfs/client1/var/swap

Then create a tar archive of the nfs folder (you could gzip it but as the Pi isn't all that powerful its probably not worth it vs transfer time, but this is just an assumption).

The -p flag should preserve permissions etc.

sudo tar -cpf /nfs.tar /nfs

I ended up with a 1.2Gb tar:

pi@raspberrypi:~ $ ls -alh /nfs.tar 
-rw-r--r-- 1 root root 1.2G Mar  2 20:56 /nfs.tar

Finally this archive needs to end up on the Server, how doesn't really matter - rsync, scp, or take the card out and copy it...

eg:

pi@raspberrypi:~ $ scp /nfs.tar paul@nas:/home/paul/nfs.tar
nfs.tar                        2%   24MB  12.9MB/s   01:25 ETA

We are now done with this Pi for now, the SD card will be used later.

There are alternate instructions below for 'cloning' an existing Pi using rsync to preserve permissions which can be quicker if you want to clone direct to your server.

Enable Network Boot

These steps enable USB Boot Mode (which includes PXE) on the Pi.

Step 1: Raspbian Lite SD Card

Either by repeating Step 1 above, or just re-using the card as left by the previous section, boot the Pi again.

Step 2: Boot Config

We must enable USB Boot Mode (which also supports PXE).

Append program_usb_boot_mode=1 to /boot/config.txt:

echo program_usb_boot_mode=1 | sudo tee -a /boot/config.txt

Which can be verified with tail /boot/config.txt:

pi@raspberrypi:~ $ tail /boot/config.txt
#dtparam=spi=on

# Uncomment this to enable the lirc-rpi module
#dtoverlay=lirc-rpi

# Additional overlays and parameters are documented /boot/overlays/README

# Enable audio (loads snd_bcm2835)
dtparam=audio=on
program_usb_boot_mode=1

Then reboot:

sudo reboot

Once the Pi is back up, check the OTP has been programmed correctly with vcgencmd otp_dump | grep 17::

pi@raspberrypi:~ $ vcgencmd otp_dump | grep 17:
17:3020000a

If the output ends with 3020000a then it has worked.

Once more we are done with the Pi and SD Card for now, you can revert the /boot/config.txt change if you wish to keep using the card for other things, the rest of the /boot partition on the SD card will still be needed later.

Actually Network Booting the Pi

In this set of steps we make the required server-side changes to allow it to boot.

Step 1: DHCP

Your local DHCP server needs to be able to send a couple of specific DHCP options.

My server is already running the ISC DHCP Server which can be installed with sudo apt-get install isc-dhcp-server.

Beforehand my DHCP configuration (/etc/dhcp/dhcpd.conf) served up IPs in the range 192.168.1.100-200 along with a specific local DNS suffix, name servers and the gateway, plus a number of IP reservations (not included below):

The address of the next-server directive has to be added: next-server 192.168.1.50;, as well as the tftp-server-name: option tftp-server-name "192.168.1.50";.

I have not come across the need for the tftp-server-name entry before when PXE booting standard (x86/x64) computers/servers.

This thread helped narrow down some of the required config changes for isc-dhcp-server.

After any changes restart the service:

sudo service isc-dhcp-server restart

DHCP activity can be viewed either by tailing syslog tail -f /var/log/syslog | grep dhcpd:

-> % tail -f /var/log/syslog | grep dhcpd
Mar  2 21:48:17 nas dhcpd[825]: DHCPDISCOVER from b8:27:eb:1b:3c:41 via em1
Mar  2 21:48:17 nas dhcpd[825]: DHCPOFFER on 192.168.1.58 to b8:27:eb:1b:3c:41 via em1
Mar  2 21:48:27 nas dhcpd[825]: DHCPDISCOVER from b8:27:eb:1b:3c:41 via em1
Mar  2 21:48:27 nas dhcpd[825]: DHCPOFFER on 192.168.1.58 to b8:27:eb:1b:3c:41 via em1

Or using tcpdump: sudo tcpdump -i em1 port bootpc

-> % sudo tcpdump -i em1 port bootpc
21:48:17.817689 IP 0.0.0.0.bootpc > 255.255.255.255.bootps: BOOTP/DHCP, Request from b8:27:eb:1b:3c:41 (oui Unknown), length 320
21:48:17.818171 IP nas.home.ridgway.io.bootps > 192.168.1.58.bootpc: BOOTP/DHCP, Reply, length 300
21:48:27.207000 IP 0.0.0.0.bootpc > 255.255.255.255.bootps: BOOTP/DHCP, Request from b8:27:eb:1b:3c:41 (oui Unknown), length 320
21:48:27.207371 IP nas.home.ridgway.io.bootps > 192.168.1.58.bootpc: BOOTP/DHCP, Reply, length 300

When the Pi is powered on after USB Boot Mode is enabled you can see it looking for an IP.

Step 2: TFTP

Install the TFTP service (which works through xinetd):

sudo apt-get install -y tftpd

Create a tftpd configuration file /etc/xinetd.d/tftp containing:

And create the /tftpboot folder specified above:

sudo mkdir /tftpboot

Then finally restart xinetd:

sudo service xinetd restart

To view activity from the TFTP server tail syslog with tail -f /var/log/syslog | grep tftpd :

Verify connectivity and logging from another host:

Client:

paul@box [21:54:00] [~] 
-> % tftp 192.168.1.50
tftp> get test
Error code 1: File not found
tftp> 

Server:

paul@nas [21:53:48] [~] 
-> % tail -f /var/log/syslog | grep tftpd 
Mar  2 21:54:04 nas tftpd[1800]: tftpd: trying to get file: test
Mar  2 21:54:04 nas tftpd[1800]: tftpd: serving file from /tftpboot

Now when the Pi is started after the DHCP entries, syslog should show some TFTP activity:

tftpd: trying to get file: bootsig.bin
tftpd: serving file from /tftpboot

Note: The log does not indicate failure due to this file not yet existing.

Copy all the files from the /boot partition of the SD card to the /tftpboot folder. If the Pi Firmware was updated as detailed above then it should be fine, however if not the latest bootcode.bin from GitHub is required.

eg:

paul@box [10:21:09] [/media/paul/boot] 
-> % 7z a boot.7z /media/paul/boot/*
paul@box [10:35:39] [/media/paul/boot] 
-> % scp boot.7z nas:
paul@nas [10:21:30] [~/boot] 
-> % 7z x ../boot.7z       
paul@nas [10:21:16] [~/boot] 
-> % sudo mv * /tftpboot

Note: As noted by a reader, linking the TFTP root to the boot folder in the NFS tree allows the firmware to be updated. Linking or copying the contents of boot to NFS also allows I2C and SPI to work because the overlays in /boot are available to the OS.

Warning: If other files from GitHub (possibly the start/fixup elf files) are used the Pi will NOT be able to see all available resources such as RAM.

The logs show the files requested, so some clean-up can be done if desired.

tftpd: trying to get file: bootcode.bin
trying to get file: bootsig.bin
tftpd: trying to get file: ea1b3c41/start.elf
tftpd: trying to get file: autoboot.txt
tftpd: trying to get file: config.txt
tftpd: trying to get file: recovery.elf
tftpd: trying to get file: start.elf
tftpd: trying to get file: fixup.dat
tftpd: trying to get file: recovery.elf
tftpd: trying to get file: config.txt
tftpd: trying to get file: dt-blob.bin
tftpd: trying to get file: recovery.elf
tftpd: trying to get file: config.txt
tftpd: trying to get file: bootcfg.txt
tftpd: trying to get file: cmdline.txt
tftpd: trying to get file: recovery8.img
tftpd: trying to get file: recovery8-32.img
tftpd: trying to get file: recovery7.img
tftpd: trying to get file: recovery.img
tftpd: trying to get file: kernel8.img
tftpd: trying to get file: kernel8-32.img
tftpd: trying to get file: kernel7.img
tftpd: trying to get file: armstub8.bin
tftpd: trying to get file: armstub8-32.bin
tftpd: trying to get file: armstub7.bin
tftpd: trying to get file: armstub.bin
tftpd: trying to get file: kernel7.img
tftpd: trying to get file: bcm2710-rpi-3-b.dtb
tftpd: trying to get file: overlays/rpi-ft5406.dtbo
tftpd: trying to get file: overlays/rpi-backlight.dtbo
tftpd: trying to get file: config.txt

All being well when the PI starts it will first show the usual gradient screen:

And then appear to get stuck waiting for the SD card:

If any of the required files are missing, DHCP is not configured correctly or it cannot reach TFTP in my experience you won't even get the gradient screen.

Step 3: NFS

Install NFS Server:

sudo apt-get install -y nfs-kernel-server

Extract the tar archive from the steps above.

Again, to preserve permissions sudo and --same-owner are important:

sudo tar --same-owner -xvf nfs.tar                        

And move the nfs folder to root:

sudo mv nfs /

And then set up the NFS export, restarting NFS after and ensuring it is enabled:

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

Now the Pi needs to be told to boot from the NFS Server, create/edit /tftpboot/cmdline.txt with the contents:

selinux=0 dwc_otg.lpm_enable=0 console=tty1 rootwait rw nfsroot=192.168.1.50:/nfs/client1,v3 ip=dhcp root=/dev/nfs elevator=deadline

This can be copied and then edited from the boot partition of the SD card too.

Finally, remove all other entries from the fstab file /nfs/client1/etc/fstab so that it only contains the proc entry:

proc            /proc           proc    defaults          0       0

The Pi should now boot fully.

You can edit /nfs/client1/etc/rc.local to make it clear the Pi has network booted:

#!/bin/sh -e
if [ "$_IP" ]; then
  printf "My NETWORK BOOTED IP address is %s\n" "$_IP"
fi

exit 0

During the boot process the logs show the network and NFS configuration which can be useful to help debug issues (the lines from IP-Config and on):

Step 4: Per Pi Configuration (Optional)

I have a TFT screen for the Pi which is upside down by default:

The fix is to add the following line to /boot/config.txt:

lcd_rotate=2

If I do that in /tftpboot/config.txt it will apply to all network booted Pis. Fortunately the Pi first checks a folder based on it's MAC address (serial number as stated here), as seen in last line below (copied from the boot sequence above):

Mar  2 22:21:19 nas tftpd[2579]: tftpd: trying to get file: bootcode.bin
Mar  2 22:21:19 nas tftpd[2581]: tftpd: trying to get file: bootsig.bin
Mar  2 22:21:19 nas tftpd[2583]: tftpd: trying to get file: ea1b3c41/start.elf

My PI has the MAC of ea:1b:3c:41 so a folder for it needs to be created:

sudo mkdir /tftpboot/ea1b3c41

If the PI finds start.elf there it will look for all other files there too, to save space they can be symlinked with sudo ln -s /tftpboot/* . which gives:

paul@nas [23:08:25] [/tftpboot/ea1b3c41] 
-> % ls -alh
total 8.0K
drwxr-xr-x 2 root root 4.0K Mar  2 23:08 .
drwxr-xr-x 3 root root 4.0K Mar  2 23:06 ..
lrwxrwxrwx 1 root root   29 Mar  2 23:07 bcm2708-rpi-0-w.dtb -> /tftpboot/bcm2708-rpi-0-w.dtb
lrwxrwxrwx 1 root root   27 Mar  2 23:07 bcm2708-rpi-b.dtb -> /tftpboot/bcm2708-rpi-b.dtb
lrwxrwxrwx 1 root root   32 Mar  2 23:07 bcm2708-rpi-b-plus.dtb -> /tftpboot/bcm2708-rpi-b-plus.dtb
lrwxrwxrwx 1 root root   28 Mar  2 23:07 bcm2708-rpi-cm.dtb -> /tftpboot/bcm2708-rpi-cm.dtb
lrwxrwxrwx 1 root root   29 Mar  2 23:07 bcm2709-rpi-2-b.dtb -> /tftpboot/bcm2709-rpi-2-b.dtb
lrwxrwxrwx 1 root root   29 Mar  2 23:07 bcm2710-rpi-3-b.dtb -> /tftpboot/bcm2710-rpi-3-b.dtb
lrwxrwxrwx 1 root root   29 Mar  2 23:07 bcm2710-rpi-cm3.dtb -> /tftpboot/bcm2710-rpi-cm3.dtb
lrwxrwxrwx 1 root root   22 Mar  2 23:07 bootcode.bin -> /tftpboot/bootcode.bin
lrwxrwxrwx 1 root root   21 Mar  2 23:07 cmdline.txt -> /tftpboot/cmdline.txt
lrwxrwxrwx 1 root root   18 Mar  2 23:08 ea1b3c41 -> /tftpboot/ea1b3c41
lrwxrwxrwx 1 root root   21 Mar  2 23:07 kernel7.img -> /tftpboot/kernel7.img
lrwxrwxrwx 1 root root   20 Mar  2 23:07 kernel.img -> /tftpboot/kernel.img
lrwxrwxrwx 1 root root   22 Mar  2 23:07 start_cd.elf -> /tftpboot/start_cd.elf
lrwxrwxrwx 1 root root   22 Mar  2 23:07 start_db.elf -> /tftpboot/start_db.elf
lrwxrwxrwx 1 root root   19 Mar  2 23:07 start.elf -> /tftpboot/start.elf
lrwxrwxrwx 1 root root   21 Mar  2 23:07 start_x.elf -> /tftpboot/start_x.elf

We can move the newly created self referencing link:

sudo rm /tftpboot/ea1b3c41/ea1b3c41

Now when that Pi boots it will pull all subsequent files from that folder:

Mar  2 23:09:41 nas tftpd[5282]: tftpd: trying to get file: bootcode.bin
Mar  2 23:09:41 nas tftpd[5284]: tftpd: trying to get file: bootsig.bin
Mar  2 23:09:41 nas tftpd[5286]: tftpd: trying to get file: ea1b3c41/start.elf
Mar  2 23:09:41 nas tftpd[5288]: tftpd: trying to get file: ea1b3c41/autoboot.txt
Mar  2 23:09:41 nas tftpd[5290]: tftpd: trying to get file: ea1b3c41/config.
...

Now a modified config.txt can be added, either copied from /tftpboot and changed, or from the /boot partition of an SD Card:

paul@nas [23:10:12] [/tftpboot/ea1b3c41] 
-> % ll
total 4.0K
lrwxrwxrwx 1 root root   29 Mar  2 23:07 bcm2708-rpi-0-w.dtb -> /tftpboot/bcm2708-rpi-0-w.dtb
lrwxrwxrwx 1 root root   27 Mar  2 23:07 bcm2708-rpi-b.dtb -> /tftpboot/bcm2708-rpi-b.dtb
lrwxrwxrwx 1 root root   32 Mar  2 23:07 bcm2708-rpi-b-plus.dtb -> /tftpboot/bcm2708-rpi-b-plus.dtb
lrwxrwxrwx 1 root root   28 Mar  2 23:07 bcm2708-rpi-cm.dtb -> /tftpboot/bcm2708-rpi-cm.dtb
lrwxrwxrwx 1 root root   29 Mar  2 23:07 bcm2709-rpi-2-b.dtb -> /tftpboot/bcm2709-rpi-2-b.dtb
lrwxrwxrwx 1 root root   29 Mar  2 23:07 bcm2710-rpi-3-b.dtb -> /tftpboot/bcm2710-rpi-3-b.dtb
lrwxrwxrwx 1 root root   29 Mar  2 23:07 bcm2710-rpi-cm3.dtb -> /tftpboot/bcm2710-rpi-cm3.dtb
lrwxrwxrwx 1 root root   22 Mar  2 23:07 bootcode.bin -> /tftpboot/bootcode.bin
lrwxrwxrwx 1 root root   21 Mar  2 23:07 cmdline.txt -> /tftpboot/cmdline.txt
-rw-r--r-- 1 root root 1.3K Mar  2 23:10 config.txt
lrwxrwxrwx 1 root root   21 Mar  2 23:07 kernel7.img -> /tftpboot/kernel7.img
lrwxrwxrwx 1 root root   20 Mar  2 23:07 kernel.img -> /tftpboot/kernel.img
lrwxrwxrwx 1 root root   22 Mar  2 23:07 start_cd.elf -> /tftpboot/start_cd.elf
lrwxrwxrwx 1 root root   22 Mar  2 23:07 start_db.elf -> /tftpboot/start_db.elf
lrwxrwxrwx 1 root root   19 Mar  2 23:07 start.elf -> /tftpboot/start.elf
lrwxrwxrwx 1 root root   21 Mar  2 23:07 start_x.elf -> /tftpboot/start_x.elf

If config.txt was already symbolically linked them remove that first.

Now it starts with the screen the right way up:

USB Boot Mode Enable / Check Card

The steps below describe how to create an SD card which will enable and verify USB Boot Mode (PXE) making it easier to configure a large number of Pi 3 devices in one go.

Step 1: Raspbian Lite SD Card

Either by repeating Step 1 above, or just re-using the card as left by the previous section, boot the Pi again.

In the following steps a few files are edited which can be done on a Pi or another machine that can edit the /boot and rootfs filesystems.

Step 2: Boot Config

As above, we must enable USB Boot Mode.

Append program_usb_boot_mode=1 to /boot/config.txt:

echo program_usb_boot_mode=1 | sudo tee -a /boot/config.txt

Which can be verified with tail /boot/config.txt:

pi@raspberrypi:~ $ tail /boot/config.txt
#dtparam=spi=on

# Uncomment this to enable the lirc-rpi module
#dtoverlay=lirc-rpi

# Additional overlays and parameters are documented /boot/overlays/README

# Enable audio (loads snd_bcm2835)
dtparam=audio=on
program_usb_boot_mode=1
Step 3: Check on boot

With a simple script the Pi can be checked on boot. The example below clears the screen to aid readability (not helpful for debugging boot issues) and then sleeps for a long time (24h), stopping the Pi from fully booting so the output is clearly seen, colour or other formatting could be added to make it more obvious!

Create /root/check-netboot.sh with the following contents:

#!/bin/bash

clear

CHECK=$(vcgencmd otp_dump | grep 17:)

if [[ "$CHECK" == "17:3020000a" ]]; then
  echo "Boot mode fine :) ($CHECK)"
else
  echo "!!! Boot mode failed, value: $CHECK"
fi

sleep 86400

Be sure to make it executable:

sudo chmod +x /root/check-netboot.sh

Edit /etc/rc.local to call check-netboot.sh:


#!/bin/sh -e

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

/root/check-netboot.sh

exit 0

From this card each Pi 3 can be booted once which should enable USB boot mode (PXE) and confirm. The script could be updated to power off if successful with poweroff after the first echo in check-netboot.sh.

A few notes

Some notes have been pulled out into mini-guides:

Shared Install

All the devices have read-write access to the NFS file system in the configuration above, so any packages or changes installed on one are available to all.

Additionally any SSH access or hosts keys as well as the /etc/passwd file are all shared so these changes will also propagate.

While this might make it easier to make global changes (ie installing packages from apt) it is slow and I have seen errors at times, though these might have been permissions related before I found a solution to that, it seems best to make changes locally and re-clone the file system.

Touchscreen

Currently it appears the Raspberry Pi touchscreen does not get detected when network booted. Resolving this is still a work in progress...

Update (2019-04-06): I finally had time to try out atftp, which does appear to have solved the touchscreen detection issue.

Per Device Configuration

The steps above to enable per-device config.txt options can also be used to override cmdline.txt (removing the symlinked version first) and specify an alternative NFS path. /etc/exports would also need to be updated.

eg:

root@nas:/tftpboot/ea1b3c41# rm cmdline.txt 
root@nas:/tftpboot/ea1b3c41# cp ../cmdline.txt .
root@nas:/tftpboot/ea1b3c41# vi cmdline.txt 

cmdline.txt now contents (note client 2):

selinux=0 dwc_otg.lpm_enable=0 console=tty1 rootwait rw nfsroot=192.168.1.50:/nfs/client2,v3 ip=dhcp root=/dev/nfs elevator=deadline

/etc/exports contains:

# /etc/exports: the access control list for filesystems which may be exported
#		to NFS clients.  See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4        gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes  gss/krb5i(rw,sync,no_subtree_check)
#
/nfs/client1 *(rw,sync,no_subtree_check,no_root_squash)
/nfs/client2 *(rw,sync,no_subtree_check,no_root_squash)

/nfs/client2 is a copy of /nfs/client1 with a modified /etc/rc.local to verify.

Cloning another Pi with rsync

Running this from the NFS server will clone as root on both sides which should preserve the permissions without the need to create or tar an /nfs folder on the device.

The first sudo allows rsync to set the right permissions and ownership on the local filesystem. The --rsync-path="sudo rsync" runs rsync as root on the Pi ensuring it has access to all files. With the default sudoers configuration it will not prompt for anything other than the pi user's SSH password.

sudo rsync -xa --progress --rsync-path="sudo rsync" --exclude '/var/swap' --stats pi@192.168.1.59:/ /nfs/client1

Remember to edit /etc/fstab in the NFS file system afterwards as well as removing /var/swap if it wasn't excluded.

A simple script to do this clone.sh:

#!/bin/bash
sudo rsync -xa --progress --rsync-path="sudo rsync" --exclude '/var/swap' --stats $1:/ $2
grep proc $2/etc/fstab > $2/etc/fstab.new
mv $2/etc/fstab.new $2/etc/fstab

Run as ./clone.sh pi@192.168.1.59 /nfs/clientX.

Permissions and Sudo

If not using rsync as above or tar with the specific permissions flags described earlier then after creating the NFS file system the permissions may be inherited from the extraction path or similar if the specific tar instructions above were not followed:

root@nas:/nfs/client1# ll
total 88
drwxr-xr-x 22 paul paul 4096 Mar  2 20:45 ./
drwxr-xr-x  4 paul paul 4096 Mar  3 09:53 ../
drwxr-xr-x  2 paul paul 4096 Mar  2 19:04 bin/
drwxr-xr-x  2 paul paul 4096 Jan  1  1970 boot/
drwxr-xr-x  3 paul paul 4096 Jan  1  1970 boot.bak/
drwxr-xr-x  2 paul paul 4096 Mar  2 20:39 dev/
drwxr-xr-x 83 paul paul 4096 Mar  3 09:59 etc/
drwxr-xr-x  3 paul paul 4096 Nov 29 01:22 home/
drwxr-xr-x 17 paul paul 4096 Mar  2 19:12 lib/
drwx------  2 paul paul 4096 Nov 29 02:35 lost+found/
drwxr-xr-x  2 paul paul 4096 Nov 29 01:06 media/
drwxr-xr-x  2 paul paul 4096 Nov 29 01:06 mnt/
drwxr-xr-x  3 paul paul 4096 Nov 29 01:22 opt/
dr-xr-xr-x  2 paul paul 4096 Jan  1  1970 proc/
drwx------  2 paul paul 4096 Mar  2 19:13 root/
drwxr-xr-x  2 paul paul 4096 Mar  2 20:44 run/
drwxr-xr-x  2 paul paul 4096 Mar  2 19:04 sbin/
drwxr-xr-x  2 paul paul 4096 Nov 29 01:06 srv/
dr-xr-xr-x  2 paul paul 4096 Jan  1  1970 sys/
drwxrwxrwt  8 root root 4096 Mar  3 09:56 tmp/
drwxr-xr-x 10 paul paul 4096 Nov 29 01:06 usr/
drwxr-xr-x 11 paul paul 4096 Mar  3 09:56 var/

To fix this give root ownership to all files except home, note the use of the UID and GID as they will not match the server entity names:

root@nas:/nfs/client1# sudo chown root:root . -Rf
root@nas:/nfs/client1# chown 1000:1000 home/pi -Rf

If trying to sudo on a network booted Pi and getting the following error:

sudo: /usr/bin/sudo must be owned by uid 0 and have the setuid bit set

Then ensure the setuid bit is set:

root@nas:/nfs/client1# chmod u+s usr/bin/sudo

Permissions can be verified against the local Ubuntu install:

root@nas:/nfs/client1# ls -alh /usr/bin/sudo
-rwsr-xr-x 1 root root 134K Jul  4  2017 /usr/bin/sudo
root@nas:/nfs/client1# ls -alh usr/bin/sudo
-rwsr-xr-x 1 root root 133K Jun  5  2017 usr/bin/sudo

This is still not perfect so it is strongly advised to use rsync or tar as described previously.

Backups

The NFS root can easily be backed-up:

root@nas:/nfs# cp client1 backup -Rf

It would be wise to back it up before making any material configuration or package changes, just in-case.

Boot Failures

If the Pi is not requesting anything from TFTP it could be a DHCP configuration problem, or a TFTP access problem.

If you see the Pi get little or no further than requesting bootcode.bin then it is possible the wrong version of the files are in the tftp folder, ensure the next branch versions are used:

Mar  2 23:11:31 nas tftpd[5385]: tftpd: trying to get file: bootcode.bin

If it gets further, but then hangs as shown further above it could be a NFS/filesystem access problem.

Beware TFTP File Caching

Not proven, however when debugging the misreported RAM issue I replaced my custom boot folder (with new symlinks) but the touchscreen Pi still appeared to pick up the old files as it was reporting lower RAM.

After an xinetd restart all was fine...

Performance

Installing and configuring packages etc is naturally going to be slower as the Pi only has 100Mbit Ethernet and the overheads of using a network-based file system.

For speed all config could be done on SD then copied over using the steps above.