Thought I should share and discuss a solution I came up with for having a readonly rootfs without having to have an initramfs. What I wanted was a readonly rootfs in its own block along with a writeable block for storing persistent changes (an overlay works great for this). My first thought was to create an initramfs to set this up but I think I came up with a better solution. The problem with an initramfs is that pretty much everything in it is just a copy of what's already in the rootfs. So the more you put into your initramfs (maybe you want it to more tools in case it fails and you want to recover) the more duplication you create (NOTE: bootloaders have the same conundrum with the kernel).
So the solution I came up with was to boot directly to the readonly rootfs and setup the overlay from there before starting systemd/sysv or whatever init program is installed. I created a small BASH script that sets up the rootfs overlay and then "exec"s to the real init process and you're done, no need for an initramfs. Since the rootfs is readonly, you can use a squashfs and you just put it in its own partition on the disk. I call the script "reinit" and it's also configurable via the kernel command line. Here's an example of what it might look like:
NOTE: in this example, I'm creating two disks, one that contains the bootloader/kernel/rootfs, and the second which is meant to hold all the changes to the rootfs. This allows you to 1) see all the changes by looking at the contents of the second disk and/or 2) swap out disks to update the rootfs and/or revert back to the stock rootfs
sda1: contains the rootfs (can be squashfs because it's readonly!)
sdb: a whole disk formatted with some filesystem that contains the upper/work dirs for the rootfs overlay
root=/dev/sda1 init=/sbin/reinit reinit.mountsrc=/dev/sdb
I thought it was a pretty neat idea. Now I don't have to bother with an initramfs, what should I have in it, should I sign it, how do I duplicate error handling with what the rootfs does etc. But I'm new to linux so there might be better ways to do what I've done here. Let me know if anyone knows of better solutions.
The reinit bash script is included below:
Code:
#!/bin/bash
#
# Support parameters (via command line or kernel parameter):
# ------------------------------------------------------------------------------
# reinit.mountsrc=<source_device>
# todo: maybe support reinit.dir (use a sub-directory in the mounted filesystem)
# todo: maybe support mounttype when an explicit mountsrc is supplied
# todo: add support for reinit.init=...
onfail() {
echo Error occurred, dropping to bash
/bin/bash
exit 1
}
trap onfail ERR
echorun() {
echo "$@"
"$@"
}
echorun mount -t proc none /proc
# process kernel and passed in command-line parameters
set -- $(cat /proc/cmdline) "$@"
for arg in "$@"; do
case "$arg" in
reinit.mountsrc=*)
mountsrc="${arg#reinit.mountsrc=}"
;;
reinit.dir=*)
dir="${arg#reinit.dir=}"
echo Error: reinit.dir not supported yet
onfail
;;
esac
done
echo "reinit.mountsrc='$mountsrc'"
#echo "reinit.dir='$dir'"
if [[ -z "$mountsrc" ]]; then
echorun mount -t tmpfs none /reinit_dir
else
# todo: maybe support an explicit mount type
echorun mount $mountsrc /reinit_dir
fi
echorun mkdir -p /reinit_dir/rootfs /reinit_dir/rootfs_upper /reinit_dir/rootfs_work
echorun mount -t overlay overlay /reinit_dir/rootfs -o lowerdir=/,upperdir=/reinit_dir/rootfs_upper,workdir=/reinit_dir/rootfs_work
echorun mkdir -p /reinit_dir/rootfs/oldroot
echorun cd /reinit_dir/rootfs
echorun pivot_root . oldroot
echorun exec /sbin/init
echo Error: reinit: exec /sbin/init failed
onfail