Skip to content

Instantly share code, notes, and snippets.

@zhongsheng94
Forked from niun/root-ro
Last active November 27, 2018 08:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zhongsheng94/71db7c397ed5f577f725f120e7b19c39 to your computer and use it in GitHub Desktop.
Save zhongsheng94/71db7c397ed5f577f725f120e7b19c39 to your computer and use it in GitHub Desktop.
Read-only Root-FS with overlayfs for Raspian
#!/bin/sh
#
# Read-only Root-FS for Raspian
#
# Modified 2018 by Zhong Sheng to support using filesystem other than tmpfs as upper filesystem
#
# Modified 2015 by Pascal Rosin to work on raspian-ua-netinst with
# overlayfs integrated in Linux Kernel >= 3.18.
#
# Originally written by Axel Heider (Copyright 2012) for Ubuntu 11.10.
# This version can be found here:
# https://help.ubuntu.com/community/aufsRootFileSystemOnUsbFlash#Overlayfs
#
# Based on scripts from
# Sebastian P.
# Nicholas A. Schembri State College PA USA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see
# <http://www.gnu.org/licenses/>.
#
#
# Changelog:
#
# v1.0.0
# - written by Axel Heider for Ubuntu 11.10
#
# v2.0.0
# - Modified to work with overlayfs integrated in Linux Kernel (>= 3.18)
# - introduce workdir needed for new overlayfs
# - change `mount --move` to `mount -o move` to drop busybox requirement
# - Tested with raspian-ua-netinst v1.0.7
# (Linux 3.18.0-trunk-rpi, Debian 3.18.5) on a Raspberry Pi.
# The aufs part is not tested!
#
# Notes:
# * no changes to the root fs are made by this script.
# * if /home/[user] is on the RO root fs and layed fs is tmpfs, files are not saved.
#
# Used Boot Parameters:
#
# disable-root-ro={some_string} - set it to *true* to mount root fs regularly
# root-ro-driver={overlay,aufs} - layered fs drvier to be used, if not specified, it will use *overlay*
# upper-device={$upper_device} - upper device, same format described in `man 5 fstab` and `man 8 mount`, if not specified, it will use *tmpfs* as upper fs
# upper-fs-driver={$upper_fs_driver} - specified the fs type of upper device, this isn't necessary when upper device is *tmpfs*
#
# Install:
#
# Setup Boot Parameters:
# - add `upper-device=${upper_device} upper-fs-driver={$upper_fs_driver}` to the line in /boot/cmdline.txt
# - add `root-ro-driver=${root-ro-driver}` to the line in /boot/cmdline.txt
#
# Update Initramfs:
# - # cp ${/path/to/root-ro} /etc/initramfs-tools/scripts/init-bottom/root-ro
# - # chmod 0755 /etc/initramfs-tools/scripts/init-bottom/root-ro
# - # echo "${root-ro-driver}" >> /etc/initramfs-tools/modules if layered fs drvier not included
# - # echo "${upper_fs_drvier}" >> /etc/initramfs-tools/modules if upper fs drvier not included
# - # update-initramfs -u
#
# Enable Initramfs:
# - # echo "initramfs ${name-of-initramfs-file} followkernel"
#
#
# Disable read-only root fs
# * option 1: kernel boot parameter "disable-root-ro=true"
# * option 2: create file "/disable-root-ro"
#
# ROOT_RO_DRIVER variable controls which driver isused for the ro/rw layering
# Supported drivers are: overlayfs, aufs
# the kernel parameter "root-ro-driver=[driver]" can be used to initialize
# the variable ROOT_RO_DRIVER. If nothing is given, overlayfs is used.
#
# no pre requirement
PREREQ=""
prereqs()
{
echo "${PREREQ}"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
# import /usr/share/initramfs-tools/scripts/functions
. /scripts/functions
MYTAG="root-ro"
DISABLE_MAGIC_FILE="/disable-root-ro"
# parse kernel boot command line
ROOT_RO_DRIVER=
DISABLE_ROOT_RO=
UPPER_DEVICE=
UPPER_FS_DRIVER=
for CMD_PARAM in $(cat /proc/cmdline); do
case ${CMD_PARAM} in
disable-root-ro=*)
DISABLE_ROOT_RO=${CMD_PARAM#disable-root-ro=}
;;
root-ro-driver=*)
ROOT_RO_DRIVER=${CMD_PARAM#root-ro-driver=}
;;
upper-device=*)
UPPER_DEVICE=${CMD_PARAM#upper-device=}
;;
upper-fs-driver=*)
UPPER_FS_DRIVER=${CMD_PARAM#upper-fs-driver=}
;;
esac
done
log_message_and_exit()
{
log_failure_msg "$1"
exit 0
}
# check if read-only root fs is disabled
[ ! -z "${DISABLE_ROOT_RO}" ] && log_message_and_exit "${MYTAG}: disabled, found boot parameter disable-root-ro=${DISABLE_ROOT_RO}"
[ -e "${rootmnt}${DISABLE_MAGIC_FILE}" ] && log_message_and_exit "${MYTAG}: disabled, found file ${rootmnt}${DISABLE_MAGIC_FILE}"
###########################################
##
## START TO MOUNT LAYERED FILESYSTEM
##
###########################################
# set those undefined var to default
if [ -z "${ROOT_RO_DRIVER}" ]; then
ROOT_RO_DRIVER=overlay
fi
if [ -z "${UPPER_DEVICE}" ]; then
UPPER_DEVICE=tmpfs-root
UPPER_FS_DRIVER=tmpfs
elif [ -z "${UPPER_FS_DRIVER}" ]; then
log_failure_msg "${MYTAG}: upper device ${UPPER_DEVICE}'s driver is unknown,fallback to tmpfs"
UPPER_DEVICE=tmpfs-root
UPPER_FS_DRIVER=tmpfs
fi
move_lower_ro_fs()
{
OLD_MOUNT_POINT="$1"
NEW_MOUNT_POINT="$2"
# make the mount point on the init root fs ${ROOT_RO}
[ -d ${NEW_MOUNT_POINT} ] || mkdir -p ${NEW_MOUNT_POINT}
if [ $? -ne 0 ]; then
log_failure_msg "${MYTAG} ERROR 4: failed to create ${NEW_MOUNT_POINT}"
return 1
fi
# root is mounted on ${rootmnt}, move it to ${ROOT_RO}.
mount -o move ${OLD_MOUNT_POINT} ${NEW_MOUNT_POINT}
if [ $? -ne 0 ]; then
log_failure_msg "${MYTAG} ERROR 7: failed to move root away from ${OLD_MOUNT_POINT} to ${NEW_MOUNT_POINT}"
return 1
fi
}
mount_upper_fs()
{
UPPER_MOUNT_POINT="$1"
UPPER_DEVICE="$2"
UPPER_FS_DRIVER="$3"
[ -d ${UPPER_MOUNT_POINT} ] || mkdir -p ${UPPER_MOUNT_POINT}
if [ $? -ne 0 ]; then
log_failure_msg "${MYTAG} ERROR 3: failed to create dir ${UPPER_MOUNT_POINT}"
return 1
fi
# mount the upper filesystem
mount -t "${UPPER_FS_DRIVER}" "${UPPER_DEVICE}" ${UPPER_MOUNT_POINT}
if [ $? -ne 0 ]; then
log_failure_msg "${MYTAG} ERROR 3: failed to mount upper fs ${UPPER_DEVICE}"
return 1
fi
}
mount_layered_fs()
{
LAYED_FS_DRIVER="$1"
ROOT_LAYERED="$2"
ROOT_UPPER="$3"
ROOT_LOWER="$4"
# check if kernel module exists
modprobe -qb ${LAYED_FS_DRIVER}
if [ $? -ne 0 ]; then
log_failure_msg "${MYTAG} ERROR 2: missing kernel module ${LAYED_FS_DRIVER} for layered fs"
return 1
fi
# mount virtual fs ${rootmnt} with rw-fs ${ROOT_RW} on top or ro-fs ${ROOT_RO}.
case ${LAYED_FS_DRIVER} in
overlay)
setup_for_overlay "${ROOT_UPPER}"
mount -t overlay -o lowerdir=${ROOT_LOWER},upperdir=${ROOT_UPPER}/upper,workdir=${ROOT_UPPER}/work overlay ${ROOT_LAYERED}
return $?
;;
# aufs)
# MOUNT_PARMS="-t aufs -o dirs=${ROOT_UPPER}:${ROOT_LOWER}=ro aufs-root ${ROOT_LAYERED}"
# ;;
*)
log_failure_msg "${MYTAG} ERROR 1: invalid LAYED_FS_DRIVER ${LAYED_FS_DRIVER}"
return 1
;;
esac
}
setup_for_overlay()
{
OVERLAY_UPPER="$1"
# create necessary folders
[ -d ${OVERLAY_UPPER}/upper ] || mkdir -p ${OVERLAY_UPPER}/upper
if [ $? -ne 0 ]; then
log_failure_msg "${MYTAG} ERROR 6.1: failed to create ${OVERLAY_UPPER}/upper"
return 1
fi
[ -d ${OVERLAY_UPPER}/work ] || mkdir -p ${OVERLAY_UPPER}/work
if [ $? -ne 0 ]; then
log_failure_msg "${MYTAG} ERROR 6.2: failed to create ${OVERLAY_UPPER}/work"
return 1
fi
}
move_mount_to_layered_root()
{
OLD_MOUNT_POINT="$1"
NEW_MOUNT_POINT="$2"
[ -d ${NEW_MOUNT_POINT} ] || mkdir -p ${NEW_MOUNT_POINT}
mount -o move ${OLD_MOUNT_POINT} ${NEW_MOUNT_POINT}
if [ $? -ne 0 ]; then
log_failure_msg "${MYTAG} ERROR 9: failed to move ${OLD_MOUNT_POINT} to ${NEW_MOUNT_POINT}"
return 1
fi
}
# generic settings
# ${ROOT} and ${rootmnt} are predefined by caller of this script. Note that
# the root fs ${rootmnt} it mounted readonly on the initrams, which fits nicely
# for our purposes.
ROOT_RO=/mnt/root-ro
ROOT_RW=/mnt/root-rw
# move lower filesystem to ${ROOT_RO}
move_lower_ro_fs "${rootmnt}" "${ROOT_RO}" \
&& log_success_msg "${MYTAG} sucessfully move lower fs to ${ROOT_RO}" \
|| panic "${MYTAG} ERROR 8: failed to move lower fs"
# mount upper filesystem
mount_upper_fs "${ROOT_RW}" "${UPPER_DEVICE}" "${UPPER_FS_DRIVER}" && \
log_success_msg "${MYTAG} sucessfully mount upper fs ${UPPER_DEVICE} to ${ROOT_RW}"
if [ $? -ne 0 ]; then
log_failure_msg "${MYTAG} INFO: fallback upper filesystem to tmpfs"
umount "${ROOT_RW}"
mount_upper_fs "${ROOT_RW}" tmpfs-root tmpfs || panic "${MYTAG} ERROR 8: failed to fallback to tmpfs"
fi
# there is nothing left at ${rootmnt} now. So for any error we get we should
# either do recovery to restore ${rootmnt} for drop to a initramfs shell using
# "panic". Otherwise the boot process is very likely to fail with even more
# errors and leave the system in a wired state.
mount_layered_fs "${ROOT_RO_DRIVER}" "${rootmnt}" "${ROOT_RW}" "${ROOT_RO}"
if [ $? -ne 0 ]; then
# thats bad, drop to shell to let the user try fixing this
panic "${MYTAG} ERROR 8: failed to create new ro/rw layerd ${rootmnt}"
exit 0
else
log_success_msg "${MYTAG} sucessfully mount ${ROOT_RO_DRIVER} layered fs"
fi
# now the real root fs is on ${ROOT_RO} of the init file system, our layered
# root fs is set up at ${rootmnt}. So we can write anywhere in {rootmnt} and the
# changes will end up in ${ROOT_RW} while ${ROOT_RO} it not touched. However
# ${ROOT_RO} and ${ROOT_RW} are on the initramfs root fs, which will be removed
# an replaced by ${rootmnt}. Thus we must move ${ROOT_RO} and ${ROOT_RW} to the
# rootfs visible later, ie. ${rootmnt}${ROOT_RO} and ${rootmnt}${ROOT_RO}.
# Since the layered ro/rw is already up, these changes also end up on
# ${ROOT_RW} while ${ROOT_RO} is not touched.
move_mount_to_layered_root "${ROOT_RO}" "${rootmnt}/${ROOT_RO}"
move_mount_to_layered_root "${ROOT_RW}" "${rootmnt}/${ROOT_RW}"
# technically, everything is set up nicely now. Since ${rootmnt} had beend
# mounted read-only on the initfamfs already, ${rootmnt}${ROOT_RO} is it, too.
# Now we init process could run - but unfortunately, we may have to prepare
# some more things here.
# Basically, there are two ways to deal with the read-only root fs. If the
# system is made aware of this, things can be simplified a lot.
# If it is not, things need to be done to our best knowledge.
#
# So we assume here, the system does not really know about our read-only root fs.
#
# Let's deal with /etc/fstab first. It usually contains an entry for the root
# fs, which is no longer valid now. We have to remove it and add our new
# ${ROOT_RO} entry.
# Remember we are still on the initramfs root fs here, so we have to work on
# ${rootmnt}/etc/fstab. The original fstab is ${rootmnt}${ROOT_RO}/etc/fstab.
## TODO
modify_fstab_on_layered_fs()
{
ROOT_TYPE=$(cat /proc/mounts | ${rootmnt}/bin/grep ${ROOT} | ${rootmnt}/usr/bin/cut -d' ' -f3)
ROOT_OPTIONS=$(cat /proc/mounts | ${rootmnt}/bin/grep ${ROOT} | ${rootmnt}/usr/bin/cut -d' ' -f4)
cat ${rootmnt}/etc/fstab | ${rootmnt}/bin/grep " ${ROOT_RO} "
if [ $? -ne 0 ];then
echo "${ROOT} ${ROOT_RO} ${ROOT_TYPE} ${ROOT_OPTIONS} 0 0" > ${rootmnt}/etc/fstab.new
fi
#remove root entry and swap from fstab
cat ${rootmnt}/etc/fstab | ${rootmnt}/bin/grep -v ' / ' | ${rootmnt}/bin/grep -v swap |${rootmnt}/bin/grep -v -F " ${ROOT_RO} " >>${rootmnt}/etc/fstab.new
${rootmnt}/bin/mv ${rootmnt}/etc/fstab.new ${rootmnt}/etc/fstab
if [ $? -ne 0 ]; then
log_failure_msg "${MYTAG} ERROR 12: failed to modify etc/fstab"
exit 0
fi
}
modify_fstab_on_layered_fs
# now we are done. Additinal steps may be necessary depending on the actualy
# distribution and/or its configuration.
log_success_msg "${MYTAG} curr mount table:\n`mount`"
log_success_msg "${MYTAG} sucessfully set up ro/tmpfs-rw layered root fs using ${ROOT_RO_DRIVER}"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment