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: