Mini Guide: Read-only boot on the ROCK Pi

Mini Guide: Read-only boot on the ROCK Pi

There are various use cases for booting a read only OS, including but not limited to Kiosk or IoT projects.

I've previously written about an overlay-based read-only Pi setup here. This Mini Guide looks at achieving the same thing on the Rock 4 series (it may work with a wider range of RockChip devices).

This has been tested on a Rock 4 SE and and Rock 4 C+.

The Guide

First, create a new init script which will be the primary boot target. The following goes in /sbin/ro-root.sh

#!/bin/sh

fail(){
	echo -e "$1"
	/bin/bash
}

echo "********************************************"
echo "** STARTING READ-ONLY FILESYSTEM ***********"
echo "********************************************"
 
# load module
modprobe overlay
if [ $? -ne 0 ]; then
    fail "ERROR: missing overlay kernel module"
fi
# mount /proc
mount -t proc proc /proc

# create a writable fs to then create our mountpoints 
mount -t tmpfs inittemp /mnt
if [ $? -ne 0 ]; then
    fail "ERROR: could not create a temporary filesystem to mount the base filesystems for overlayfs"
fi

mkdir -p /oldroot
mount -t tmpfs inittemp /oldroot
if [ $? -ne 0 ]; then
    fail "ERROR: could not create a temporary filesystem for the oldroot"
fi

mkdir /mnt/lower
mkdir /mnt/rw
mount -t tmpfs root-rw /mnt/rw
if [ $? -ne 0 ]; then
    fail "ERROR: could not create tempfs for upper filesystem"
fi

mkdir /mnt/rw/upper
mkdir /mnt/rw/work
mkdir /mnt/newroot

# mount root filesystem readonly 
rootDev=`awk '$2 == "/" {print $1}' /proc/mounts`
rootMountOpt=`awk '$2 == "/" {print $4}' /proc/mounts`
rootFsType=`awk '$2 == "/" {print $3}' /proc/mounts`
mount -o remount,ro /
mount -t ${rootFsType} -o ${rootMountOpt},ro ${rootDev} /mnt/lower
if [ $? -ne 0 ]; then
    echo '****************'
    cat /proc/mounts
    echo '****************'

    fail "ERROR: could not ro-mount original root partition"
fi

mount -t overlay -o lowerdir=/mnt/lower,upperdir=/mnt/rw/upper,workdir=/mnt/rw/work overlayfs-root /mnt/newroot
if [ $? -ne 0 ]; then
    fail "ERROR: could not mount overlayFS"
fi
# create mountpoints inside the new root filesystem-overlay
mkdir -p /mnt/newroot/ro
mkdir -p /mnt/newroot/rw

echo "remove root mount from fstab (this is already a non-permanent modification)"
echo "Root Dev: $rootDev"
grep -vE "/\s+ext4" /mnt/lower/etc/fstab > /mnt/newroot/etc/fstab
echo "#the original root mount has been removed by overlayRoot.sh" >> /mnt/newroot/etc/fstab
echo "#this is only a temporary modification, the original fstab" >> /mnt/newroot/etc/fstab
echo "#stored on the disk can be found in /ro/etc/fstab" >> /mnt/newroot/etc/fstab

echo change to the new overlay root
cd /mnt/newroot

pivot_root . oldroot
exec chroot . sh -c "$(cat <<END
set -x

echo "Running final chrooted script"

echo move ro and rw mounts to the new root

rm /ro
mkdir /ro
mount --move /oldroot/mnt/lower/ /ro
if [ $? -ne 0 ]; then
    echo "ERROR: could not move ro-root into newroot"
    /bin/bash
fi

mount --move /oldroot/mnt/rw /rw
if [ $? -ne 0 ]; then
    echo "ERROR: could not move tempfs rw mount into newroot"
    /bin/bash
fi

mount --move /oldroot/proc /proc
if [ $? -ne 0 ]; then
    echo "ERROR: could not move proc rw mount into newroot"
    /bin/bash
fi

mount --move /oldroot/dev /dev
if [ $? -ne 0 ]; then
    echo "ERROR: could not move dev rw mount into newroot"
    /bin/bash
fi

echo unmount unneeded mounts so we can unmout the old readonly root

umount -l -f /oldroot/run
umount -l -f /oldroot/dev
umount -l -f /oldroot/mnt
umount -l -f /oldroot/oldroot
rmdir /oldroot

echo "Mount /ro as read-write for selective persistent changes"
mount -o remount,rw /ro

# Optional, link /srv with direct access
echo "Read/write location (mostly config)"
rm -Rf /srv
ln -sd /ro/srv /srv

echo "continue with regular init"
exec /sbin/init
END

)"

This creates the overlay filesystem and pivots the root mount to the overlay mount.

The /ro path allows direct access to the underlying data, in this example it is mounted with read/write access for easy but selective persistent changes.

It also links /srv to /ro/srv so services updates can be applied directly.

Ensure the script is executable:

chmod +x /sbin/ro-root.sh

Now, update the boot configuration. On the Rocks this can be found at /boot/extlinux/extlinux.conf. Change init=/bin/init to init=/sbin/ro-root.sh on the last line:

#timeout 10
#menu title select kernel

label kernel-4.4.194-11-rk3399-rockchip-g1bb08d49cc40
    kernel /vmlinuz-4.4.194-11-rk3399-rockchip-g1bb08d49cc40
    initrd /initrd.img-4.4.194-11-rk3399-rockchip-g1bb08d49cc40
    devicetreedir /dtbs/4.4.194-11-rk3399-rockchip-g1bb08d49cc40
    append earlyprintk console=ttyFIQ0,1500000n8 rw init=/sbin/ro-root.sh rootfstype=ext4 rootwait  root=UUID=a68ac65b-99c7-4945-9886-310bab475861 console=ttyS2,1500000n8

Now the Rock will execute the ro-root.sh script before moving on to the usual init binary.

The changes change be verified after a reboot by checking the mounts, for example with df:

rock@rockpi-4b:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            1.9G  8.0K  1.9G   1% /dev
/dev/mmcblk0p5   30G  1.4G   27G   5% /ro
root-rw         1.9G  344K  1.9G   1% /rw
overlayfs-root  1.9G  344K  1.9G   1% /
tmpfs           1.9G     0  1.9G   0% /dev/shm
tmpfs           382M  5.3M  377M   2% /run
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
tmpfs           1.9G     0  1.9G   0% /sys/fs/cgroup
tmpfs           382M     0  382M   0% /run/user/0
tmpfs           382M     0  382M   0% /run/user/1000
/dev/mmcblk0p4  511M   42M  470M   9% /boot

The / (root) mount is on the overlay filesystem. We have /ro and /rw which overlay uses and for the base data and temporary changes respectively.

So now, any changes will not persist between power cycles, unless it is made directly under /ro.

To install packages, etc, simply sudo chroot /ro then run the desired commands such as apt update or apt install ....

Troubleshooting

The Rock boot is different to the classic Pi, the graphics do not initialise until it reaches the login prompt, meaning there are no boot messages, errors or otherwise on screen.

To view all the boot logging use the serial console as described here in the wiki: