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

Raspbian Stretch Full image

Raspbian Stretch Lite image

Jumper (or F-F jumper cable)

Lots of hard disk space

Debian/Raspbian OS

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.

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:

UUID RESTORE=$(uuidgen)

UUIDROOTFS=$(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 ${UUID RESTORE}

e2label /dev/loop0p2 recoveryfs

tune2fs /dev/loop0p3 -U ${UUIDROOTFS}

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/restore boot

mkdir -p mnt/restorerecovery

mkdir -p mnt/restore_rootfs

mount /dev/loop0p1 mnt/restore boot

mount /dev/loop0p2 mnt/restorerecovery

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.

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/restore boot/boot to root

chmod +x mnt/restoreboot/boot to recovery

chmod +x mnt/restore boot/restore root

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/restore rootfs/etc/fstab

proc /proc proc defaults 0 0

UUID=${UUIDBOOT} /boot vfat defaults 0 2

UUID=${UUID_ROOTFS} / ext4 defaults,noatime 0 1

EOF

cat << EOF > mnt/restore recovery/etc/fstab

proc /proc proc defaults 0 0

UUID=${UUIDBOOT} /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/restore boot

umount -f mnt/restorerecovery

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/boot to recovery

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

sudo ./boot/boot to rootfs

To perform a fully automatic restore:

sudo ./boot/boot to recovery 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 check reset gpio.py code in /boot and make sure it runs on boot:

nano /etc/rc.local

Before the exit 0 line, add:

python3 /boot/check reset gpio.py

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

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")