|
#!/bin/sh |
|
# |
|
# Read-only Root-FS for Raspian |
|
# |
|
# 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! |
|
# |
|
# v2.1.0 |
|
# - Add cli prompt mode indicator (Andy Chentsov <chentsov@gmail.com>) |
|
# |
|
# Notes: |
|
# * no changes to the root fs are made by this script. |
|
# * if /home/[user] is on the RO root fs, files are in ram and not saved. |
|
# |
|
# Install: |
|
# echo overlay >> /etc/initramfs-tools/modules |
|
# put this file in /etc/initramfs-tools/scripts/init-bottom/root-ro |
|
# chmod 0755 /etc/initramfs-tools/scripts/init-bottom/root-ro |
|
# update-initramfs -u |
|
# add `root-ro-driver=overlay` to the line in /boot/cmdline.txt |
|
# |
|
# 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= |
|
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=} |
|
;; |
|
esac |
|
done |
|
|
|
#echo "ROOT-RO Driver: ${ROOT_RO_DRIVER}" |
|
|
|
# check if read-only root fs is disabled |
|
if [ ! -z "${DISABLE_ROOT_RO}" ]; then |
|
# echo "${MYTAG}: disabled, found boot parameter disable-root-ro=${DISABLE_ROOT_RO}" |
|
log_warning_msg "${MYTAG}: disabled, found boot parameter disable-root-ro=${DISABLE_ROOT_RO}" |
|
exit 0 |
|
fi |
|
if [ -e "${rootmnt}${DISABLE_MAGIC_FILE}" ]; then |
|
# echo "${MYTAG}: disabled, found file ${rootmnt}${DISABLE_MAGIC_FILE}" |
|
log_warning_msg "${MYTAG}: disabled, found file ${rootmnt}${DISABLE_MAGIC_FILE}" |
|
exit 0 |
|
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 |
|
ROOT_RW_UPPER=${ROOT_RW}/upper |
|
ROOT_RW_WORK=${ROOT_RW}/work |
|
|
|
# check if ${ROOT_RO_DRIVER} is defined, otherwise set default |
|
if [ -z "${ROOT_RO_DRIVER}" ]; then |
|
ROOT_RO_DRIVER=overlay |
|
fi |
|
# settings based in ${ROOT_RO_DRIVER}, stop here if unsupported. |
|
case ${ROOT_RO_DRIVER} in |
|
overlay) |
|
MOUNT_PARMS="-t overlay -o lowerdir=${ROOT_RO},upperdir=${ROOT_RW_UPPER},workdir=${ROOT_RW_WORK} overlay ${rootmnt}" |
|
;; |
|
aufs) |
|
MOUNT_PARMS="-t aufs -o dirs=${ROOT_RW}:${ROOT_RO}=ro aufs-root ${rootmnt}" |
|
;; |
|
*) |
|
# echo "${MYTAG} ERROR 1: invalid ROOT_RO_DRIVER ${ROOT_RO_DRIVER}" |
|
panic "${MYTAG} ERROR 1: invalid ROOT_RO_DRIVER ${ROOT_RO_DRIVER}" |
|
;; |
|
esac |
|
|
|
|
|
# check if kernel module exists |
|
modprobe -qb ${ROOT_RO_DRIVER} |
|
if [ $? -ne 0 ]; then |
|
# echo "${MYTAG} ERROR 2: missing kernel module ${ROOT_RO_DRIVER}" |
|
log_failure_msg "${MYTAG} ERROR 2: missing kernel module ${ROOT_RO_DRIVER}" |
|
exit 0 |
|
fi |
|
|
|
# make the mount point on the init root fs ${ROOT_RW} |
|
[ -d ${ROOT_RW} ] || mkdir -p ${ROOT_RW} |
|
if [ $? -ne 0 ]; then |
|
# echo "${MYTAG} ERROR 3: failed to create ${ROOT_RW}" |
|
log_failure_msg "${MYTAG} ERROR 3: failed to create ${ROOT_RW}" |
|
exit 0 |
|
fi |
|
|
|
# make the mount point on the init root fs ${ROOT_RO} |
|
[ -d ${ROOT_RO} ] || mkdir -p ${ROOT_RO} |
|
if [ $? -ne 0 ]; then |
|
# echo "${MYTAG} ERROR 4: failed to create ${ROOT_RO}" |
|
log_failure_msg "${MYTAG} ERROR 4: failed to create ${ROOT_RO}" |
|
exit 0 |
|
fi |
|
|
|
# make the mount point on the init root fs ${ROOT_WORKDIR} |
|
[ -d ${ROOT_WORKDIR} ] || mkdir -p ${ROOT_WORKDIR} |
|
if [ $? -ne 0 ]; then |
|
# echo "${MYTAG} ERROR 5: failed to create ${ROOT_WORKDIR}" |
|
log_failure_msg "${MYTAG} ERROR 5: failed to create ${ROOT_WORKDIR}" |
|
exit 0 |
|
fi |
|
|
|
|
|
# mount a tempfs using the device name tmpfs-root at ${ROOT_RW} |
|
mount -t tmpfs tmpfs-root ${ROOT_RW} |
|
if [ $? -ne 0 ]; then |
|
# echo "${MYTAG} ERROR 6: failed to create tmpfs" |
|
log_failure_msg "${MYTAG} ERROR 6: failed to create tmpfs" |
|
exit 0 |
|
fi |
|
|
|
if [ "${ROOT_RO_DRIVER}" = "overlay" ]; then |
|
|
|
[ -d ${ROOT_RW_UPPER} ] || mkdir -p ${ROOT_RW_UPPER} |
|
if [ $? -ne 0 ]; then |
|
log_failure_msg "${MYTAG} ERROR 6.1: failed to create ${ROOT_RW_UPPER}" |
|
exit 0 |
|
fi |
|
|
|
[ -d ${ROOT_RW_WORK} ] || mkdir -p ${ROOT_RW_WORK} |
|
if [ $? -ne 0 ]; then |
|
log_failure_msg "${MYTAG} ERROR 6.2: failed to create ${ROOT_RW_WORK}" |
|
exit 0 |
|
fi |
|
|
|
fi |
|
|
|
|
|
# root is mounted on ${rootmnt}, move it to ${ROOT_RO}. |
|
mount -o move ${rootmnt} ${ROOT_RO} |
|
if [ $? -ne 0 ]; then |
|
log_failure_msg "${MYTAG} ERROR 7: failed to move root away from ${rootmnt} to ${ROOT_RO}" |
|
exit 0 |
|
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 virtual fs ${rootmnt} with rw-fs ${ROOT_RW} on top or ro-fs ${ROOT_RO}. |
|
mount ${MOUNT_PARMS} |
|
if [ $? -ne 0 ]; then |
|
log_failure_msg "${MYTAG} ERROR 8: failed to create new ro/rw layerd ${rootmnt}" |
|
# do recovery and try resoring the mount for ${rootmnt} |
|
mount -o move ${ROOT_RO} ${rootmnt} |
|
if [ $? -ne 0 ]; then |
|
# thats bad, drop to shell to let the user try fixing this |
|
panic "${MYTAG} RECOVERY ERROR: failed to move ${ROOT_RO} back to ${rootmnt}" |
|
fi |
|
exit 0 |
|
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 from ${ROOT_RO} to ${rootmnt}${ROOT_RO} |
|
[ -d ${rootmnt}${ROOT_RO} ] || mkdir -p ${rootmnt}${ROOT_RO} |
|
mount -o move ${ROOT_RO} ${rootmnt}${ROOT_RO} |
|
if [ $? -ne 0 ]; then |
|
log_failure_msg "${MYTAG} ERROR 9: failed to move ${ROOT_RO} to ${rootmnt}${ROOT_RO}" |
|
exit 0 |
|
fi |
|
|
|
# move mount from ${ROOT_RW} to ${rootmnt}${ROOT_RW} |
|
[ -d ${rootmnt}${ROOT_RW} ] || mkdir -p ${rootmnt}${ROOT_RW} |
|
mount -o move ${ROOT_RW} ${rootmnt}${ROOT_RW} |
|
if [ $? -ne 0 ]; then |
|
log_failure_msg "${MYTAG}: ERROR 10: failed to move ${ROOT_RW} to ${rootmnt}${ROOT_RW}" |
|
exit 0 |
|
fi |
|
|
|
# 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. |
|
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 <<EOF >${rootmnt}/etc/fstab |
|
# |
|
# This fstab is in RAM, the real one can be found at ${ROOT_RO}/etc/fstab |
|
# The original entry for '/' and all swap files have been removed. The new |
|
# entry for the read-only the real root fs follows. Write access can be |
|
# enabled using: |
|
# sudo mount -o remount,rw ${ROOT_RO} |
|
# re-mounting it read-only is done using: |
|
# sudo mount -o remount,ro ${ROOT_RO} |
|
# |
|
|
|
${ROOT} ${ROOT_RO} ${ROOT_TYPE} ${ROOT_OPTIONS} 0 0 |
|
|
|
# |
|
# remaining entries from the original ${ROOT_RO}/etc/fstab follow. |
|
# |
|
EOF |
|
if [ $? -ne 0 ]; then |
|
log_failure_msg "${MYTAG} ERROR 11: failed to modify /etc/fstab (step 1)" |
|
#exit 0 |
|
fi |
|
|
|
#remove root entry and swap from fstab |
|
cat ${rootmnt}${ROOT_RO}/etc/fstab | ${rootmnt}/bin/grep -v ' / ' | ${rootmnt}/bin/grep -v swap >>${rootmnt}/etc/fstab |
|
if [ $? -ne 0 ]; then |
|
log_failure_msg "${MYTAG} ERROR 12: failed to modify etc/fstab (step 2)" |
|
#exit 0 |
|
fi |
|
|
|
# now we are done. Additinal steps may be necessary depending on the actualy |
|
# distribution and/or its configuration. |
|
|
|
log_success_msg "${MYTAG} sucessfully set up ro/tmpfs-rw layered root fs using ${ROOT_RO_DRIVER}" |
|
|
|
# set prompt indicator |
|
cat << "EOF" >${rootmnt}/etc/profile.d/root-ro.sh |
|
|
|
PS1='(ro) ${debian_chroot:+($debian_chroot)}\u@\h:\w' |
|
if [ "`id -u`" -eq 0 ]; then |
|
PS1+='# ' |
|
else |
|
PS1+='$ ' |
|
fi |
|
|
|
EOF |
|
|
|
exit 0 |