Make a self-healing Raspberry Pi: create a recovery partition

By Lucy Hattersley. Posted

Get back to a fresh install with no need to wipe the microSD card, download images, or use another computer

Most modern operating systems come with a ‘recovery partition’, a reserved area of the drive containing everything needed to get the machine back to a clean install. So, if something goes badly wrong, you can start over. In the world of Raspbian, this normally means overwriting the image on the microSD card. This is perfectly fine, but what if you have a large number to do, say a classroom’s worth, or you don’t have access to another device to do the burning? We’re going to create an alternative version of Raspbian featuring a recovery partition. Raspberry Pi, heal thyself!

Raspberry Pi recovery partition: you'll need

 Create a custom image for a classroom and know you can quickly get back to a clean, working machine after each session

Prepare your workspace

This tutorial will describe how to create a bootable image featuring a restore partition, but there’s also a script to automate the process that you can download from magpi.cc/junkPr. This also contains the code shown some of the trickier commands shown later.

Make sure you have uuidgen installed by running it from the command line. If not, run:

sudo apt install uuid-runtime

Most commands here will need to be run as root. To avoid having to enter ‘sudo’ every time, you can switch to root using:

sudo su

Create a working directory on your machine and make sure you’ve downloaded both the Raspbian Full and Raspbian Lite images (we’re using 2019‑04-08). Unzip them as follows:

unzip 2019-04-08-raspbian-stretch-full.zip
unzip 2019-04-08-raspbian-stretch-lite.zip

Calculate the image size

Our image needs to be big enough for Raspbian Full, including its boot partition, and a second partition containing Raspbian Lite with an image of Raspbian Full. We measure disk sizes in sectors, each one 512 bytes in size. Find out how many sectors are used by the partitions:

fdisk -lu 2019-04-08-raspbian-stretch-full.img
fdisk -lu 2019-04-08-raspbian-stretch-lite.img

Each command produces output detailing how many sectors are required (see Figure 1). The boot partition starts at sector 8192 to allow for the file allocation table. To calculate the size needed:

8192 + Raspbian Full Boot Partition + Raspbian Lite Main Partition + (Raspbian Full Main Partition × 2)

With these Raspbian versions, you will need 24,426,283 sectors.

 Figure 1 Use fdisk to view the partitions and calculate the sizes you need

Create the blank image

We now need to create an empty file to contain our disk image. First convert the number of sectors required into 4MB blocks like so:

24,426,283 × 512 bytes = 12,506,256,896 bytes

12,506,256,896 / 4,194,304 = 2,982 4-megabyte blocks (rounded up)

Now create your target image:

dd if=/dev/zero bs=4M count=2982 status=progress > 2019-04-08-raspbian-stretch-full.restore.img

You now have a large file full of zeroes.

Partition the image

Let’s turn our blank file into a disk image. Start by generating some unique identifiers for the partitions:

UUIDRESTORE=$(uuidgen)
UUID
ROOTFS=$(uuidgen)
PARTUUID=$(tr -dc 'a-f0-9' < /dev/urandom 2>/dev/null | head -c8)

Now create the partition table:

sfdisk 2019-04-08-raspbian-stretch-full.restore.img <<EOL
label: dos
label-id: 0x${PARTUUID}
unit: sectors

2019-04-08-raspbian-stretch-full.restore.img1 : start=8192, size=87851, type=c
2019-04-08-raspbian-stretch-full.restore.img2 : start=96043, size=13877248, type=83
2019-04-08-raspbian-stretch-full.restore.img3 : start=13973291, size=10452992, type=83
EOL

Careful! The sizes used here are specific to the version of Raspbian used. Other versions will have different sizes. Use fdisk to calculate them.

Mount the images

Our file of zeroes can now be accessed as a disk. We’ll use the ‘loopback’ system so we can access it, along with the two versions of Raspbian.

losetup -v -f 2019-04-08-raspbian-stretch-full.restore.img
partx -v --add /dev/loop0
losetup --show -f -P 2019-04-08-raspbian-stretch-lite.img
losetup --show -f -P 2019-04-08-raspbian-stretch-full.img

Now copy over the boot and root partitions from our Raspbian Full image to partitions one and three of the new image:

dd if=/dev/loop2p1 of=/dev/loop0p1 status=progress bs=4M
dd if=/dev/loop2p2 of=/dev/loop0p3 status=progress bs=4M

We can now install Raspbian Lite on the second partition:

dd if=/dev/loop1p2 of=/dev/loop0p2 status=progress bs=4M

Configure and mount partitions

First, assign new unique IDs to the partitions and rename the recovery partition so we can tell them apart.

tune2fs /dev/loop0p2 -U ${UUIDRESTORE}
e2label /dev/loop0p2 recoveryfs
tune2fs /dev/loop0p3 -U ${UUID
ROOTFS}

Although we have allocated enough space for the recovery partition to hold a copy of the Raspbian Full image, copying over the Lite image has reduced it. Luckily it’s easy to fix this:

e2fsck -f /dev/loop0p2
resize2fs /dev/loop0p2

Now we’re in a position to mount the new image’s file systems:

mkdir -p mnt/restoreboot
mkdir -p mnt/restore
recovery
mkdir -p mnt/restore_rootfs

mount /dev/loop0p1 mnt/restoreboot
mount /dev/loop0p2 mnt/restore
recovery
mount /dev/loop0p3 mnt/restore_rootfs

Set the boot partition

Currently our image would not boot as it doesn’t know which partition to use. Run this command and make a note of the eight characters after ‘Disk identifier: 0x’.

fdisk -lu 2019-04-08-raspbian-stretch-full.restore.img

Then edit the cmdline.txt file to reset it:

nano mnt/restore_boot/cmdline.txt

Change the eight-character code after PARTUUID= to the value you noted and change the following -02 to -03, telling Raspbian to boot to the third partition.

 Our recovery partition sits between the boot partition and main file system

Create the reset scripts

To restore Raspbian your Raspberry Pi needs to boot to the second partition, containing Raspbian Lite, then overwrite the third partition with a snapshot image. We can automate this using scripts. Create the three scripts here in the mnt/restore_boot directory and make them executable:

chmod +x mnt/restoreboot/boottoroot
chmod +x mnt/restore
boot/boottorecovery
chmod +x mnt/restoreboot/restoreroot

Now make restore_root run at boot time on the recovery partition:

nano mnt/restore_recovery/etc/rc.local

Before the exit 0 line, add:

/boot/restore_root

Fix fstab

Each of the main partitions has an fstab file which tells Raspbian what disks to mount and where. This needs correcting to match our new layout:

UUID_BOOT=$(blkid -o export /dev/loop0p1 | egrep 'UUID=' | cut -d'=' -f2)

cat << EOF > mnt/restorerootfs/etc/fstab
proc /proc proc defaults 0 0
UUID=${UUID
BOOT} /boot vfat defaults 0 2
UUID=${UUID_ROOTFS} / ext4 defaults,noatime 0 1
EOF

cat << EOF > mnt/restorerecovery/etc/fstab
proc /proc proc defaults 0 0
UUID=${UUID
BOOT} /boot vfat defaults 0 2
UUID=${UUID_RESTORE} / ext4 defaults,noatime 0 1
EOF

Take a snapshot

As Raspbian Full has never been booted, it’s a perfect time to make a copy of it for restoration. This command makes a copy of the main partition and saves it in the recovery partition as a file.

dd if=/dev/loop0p3 of=mnt/restore_recovery/rootfs.img status=progress bs=4M

Now unmount everything:

umount -f mnt/restoreboot
umount -f mnt/restore
recovery
umount -f mnt/restore_rootfs
losetup --detach-all

Burn and test

You should now have a complete and ready-to-go image. Copy it over to a suitably sized microSD card (Raspbian-specific example):

dd bs=4M if=2019-04-08-raspbian-stretch-full.restore.img of=/dev/sda conv=fsync status=progress

…or you can use any burning tool, such as Etcher. Your SD card should now boot as normal. To test partition swapping, open up a Terminal and type:

sudo ./boot/boottorecovery

The Raspberry Pi should reboot into Raspbian Lite. To go back:

sudo ./boot/boottorootfs

To perform a fully automatic restore:

sudo ./boot/boottorecovery restore

Physical reset

What if you can’t get terminal access to your Raspberry Pi? A Python script that runs on boot can check the state of a GPIO pin; if shorted, the restore process is triggered. Enter the checkresetgpio.py code in /boot and make sure it runs on boot:

nano /etc/rc.local

Before the exit 0 line, add:

python3 /boot/checkresetgpio.py

To trigger the restore, use a jumper wire between GND and GPIO 21 pins and boot your Raspberry Pi.

 This project makes an ideal companion to last month’s Keyring Pi – a Zero-to-go that can self-heal!

Raspberry Pi recovery partition code and scripts

Click here to download the scripts and Python code used in this tutorial.

#!/bin/bash
if [ "$EUID" -ne 0 ]
  then echo "Please run as root"
  exit
fi

echo Rebooting to root partition in 5 seconds
sleep 5
sed -i 's/-02/-03/' /boot/cmdline.txt
touch /boot/ssh
reboot
exit 0
#!/bin/bash
if [ "$EUID" -ne 0 ]
  then echo "Please run as root"
  exit
fi

echo Rebooting to recovery partition in 5 seconds

if [ "$1" = "restore" ]; then
    echo Automatic restore selected
    touch /boot/restore
fi

sleep 5
sed -i 's/-03/-02/' /boot/cmdline.txt
touch /boot/ssh
reboot

exit 0
#!/bin/bash
if [ -f "/boot/restore" ]; then
    echo Restoring rootfs
    dd if=/rootfs.img of=/dev/mmcblk0p3 conv=fsync status=progress bs=4M
    unlink /boot/restore
    /boot/boot_to_root
fi
exit 0
import os
from gpiozero import Button

button = Button(21)

if button.is_pressed:
    print("Restore button is pressed")
    os.system("/boot/boot_to_recovery restore")
else:
    print("Restore button is not pressed")

 

More articles from The MagPi magazine

Subscribe