-
-
Save kidapu/a03dd5bb8f4ac6a4c7e69c28bacde1d3 to your computer and use it in GitHub Desktop.
Read-only Root-FS with overlayfs for Raspian
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# | |
# Read-only Root-FS for Raspian | |
# | |
# Modified 2016 by Stefan Bonfert to make it compatible with Raspbian | |
# Jessie (vanilla). | |
# | |
# 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, files are in ram and not saved. | |
# | |
# Install: | |
# put this file in /etc/initramfs-tools/scripts/init-bottom/root-ro | |
# chmod 0755 /etc/initramfs-tools/scripts/init-bottom/root-ro | |
# add `overlay` to /etc/initramfs-tools/modules | |
# mkinitramfs -o /boot/initrd | |
# add `root-ro-driver=overlay` to the line in /boot/cmdline.txt | |
# add the following to /boot/config.txt: | |
# initramfs initrd followkernel | |
# ramfsfile=initrd | |
# ramfsaddr=-1 | |
# | |
# Disable read-only root fs | |
# * option 1: kernel boot parameter "disable-root-ro=true" ( add to the line in /boot/cmdline.txt ) | |
# * 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 | |
# check if read-only root fs is disabled | |
if [ ! -z "${DISABLE_ROOT_RO}" ]; then | |
log_warning_msg "${MYTAG}: disabled, found boot parameter disable-root-ro=${DISABLE_ROOT_RO}" | |
exit 0 | |
fi | |
if [ -e "${rootmnt}${DISABLE_MAGIC_FILE}" ]; then | |
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 initramfs, 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}" | |
;; | |
*) | |
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 | |
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 | |
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 | |
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 | |
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 | |
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} has been | |
# mounted read-only on the initfamfs already, ${rootmnt}${ROOT_RO} is, too. | |
# Now the 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}" | |
exit 0 |
# Disable read-only root fs
# * option 2: create file "/disable-root-ro"
This is impossible to do if the filesystem is ro, because this file will be deleted on reboot and therefore the ro will not be disabled.
# Disable read-only root fs
# * option 2: create file "/disable-root-ro"
Could it be possible to do it the following way:
sudo mount -o remount,rw ${ROOT_RO}
sudo touch ${ROOT_RO}/disable-root-ro
sudo reboot now
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Is there a way to omit certain directories from the read-only overlay? I would like to use docker which seems to require a writable
/var/lib/docker
directory that is not an overlay…