#!/bin/bash # Copyright 2020 Jookia <contact@jookia.org> # SPDX-License-Identifier: GPL-3.0-or-later # This scripts sets up an OverlayFS using a read-only rootfs mounted by Linux. # Example usage: Save as /preinit and pass init=/preinit to your kernel. set -e # Safety feature: Error out if we hit any errors # Step 0: Check that this is running in the proper environment # We don't want to run a bunch of complicated system-altering code accidentally if [[ "$$" != "1" ]]; then echo 'Usage: Run as PID 1 before booting your system!' 1>&2 exit 1 fi # Step 1: Set up a temporary root on a tmpfs on /run # Since root is read-only, we'll use /run for our tmpfs mount # This tmpfs will store all our temporary files used for OverlayFS operation mount -t tmpfs none /run # Step 2: Mount and initialize the data partition at /run/data # For OverlayFS we need a partition (/dev/mmcblk2p5 in our case) and two # directories: overlay_root (contains the changes to the read-only root filesystem) # as well as overlay_work which is used as a temporary store when writing files # So mount and create these directories. mkdir /run/data mount /dev/mmcblk2p5 /run/data for i in overlay_root overlay_work; do mkdir -p /run/data/$i; done # Step 3: Prepare for pivoting # In a moment we're going to move the current root to /run/old_root and # temporarily chroot to /run for reasons explained in the next step. But this # is going to break running applications since we don't have bin, sbin or # lib directories in /run. So set PATH and create a /lib link them now that # will correctly resolve once we chroot. PATH=$PATH:/old_root/bin:/old_root/sbin ln -s /old_root/lib /run/lib # Step 4: Pivot our current root # When using OverlayFS we have to make sure both the lower directory (in our # case the read-only root) and upper directory (in our case the data # partition's overlay root) don't contain each other. # In our case the lower does: It's the root directory / which contains # /run/data/overlay_root. # The solution to this is to move the current root mount to /run/old_root. # The util-linux provides a tool for this: pivot_root. # In our case the pivot_root utility will move our read-only root filesystem # from / to /run/old_root and chroot all running processes to /run. # In order for this new root to work we need to have /bin, /sbin and /lib in # /run with working programs so we can continue execution by calling a shell. # In step 3 we set up links to the now pivoted old_root's bin, sbin and lib # directories so everything will work fine. mkdir /run/old_root /run/new_root pivot_root /run/ /run/old_root # Step 5: Mount OverlayFS on /new_root (previously /run/new_root) # At this point we have the following directories of interest: # - /old_root containing our read-only root filesystem # - /data/overlay_root containing our writeable root filesystem # - /data/overlay_work containing the work directory for OverlayFS root # - /new_root which we just created to place the OverlayFS root # When making the OverlayFS root, we use the /old_root as the lowerdir, # /data/overlay_root as the upperdir, and /data/overlay_work as the workdir. mount -t overlay overlay -o lowerdir=/old_root,upperdir=/data/overlay_root,workdir=/data/overlay_work /new_root # Step 6: Chroot to /new_root and run /sbin/init within the root # This finally boots the system from the overlay root. Yay! # Since we chroot instead of pivoting root, we leave our /run tmpfs in memory # but no longer accessible. This shouldn't take up much memory given it's just # a few mounts and symbolic links. exec chroot /new_root /sbin/init