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:
UUIDRESTORE=$(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 ${UUIDRESTORE}
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/restoreboot
mkdir -p mnt/restorerecovery
mkdir -p mnt/restore_rootfs
mount /dev/loop0p1 mnt/restoreboot
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/restoreboot/boottoroot
chmod +x mnt/restoreboot/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=${UUIDBOOT} /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=${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/restoreboot
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/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.
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")