Skip to content

Instantly share code, notes, and snippets.

@andyduke
Last active January 26, 2018 18:49
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 andyduke/3da146828e1fded905932ee56225802f to your computer and use it in GitHub Desktop.
Save andyduke/3da146828e1fded905932ee56225802f to your computer and use it in GitHub Desktop.
Debian OMV on Read-only root

Setting up a read-only root partition on Debian with OMV installed

To switch the root partition to read-only mode, and the system and OMV work correctly, you must use overlayfs, which is supported in the linux 3.18 kernel. I used a slightly modified script (root-ro) for Raspberry Pi by Pascal Rosin https://gist.github.com/niun/34c945d70753fc9e2cc7

To save the OMV changes, I created an omv-apply-config script that remount the lowerdir of overlayfs into read-write mode, synchronize the config.xml and exec omv-mkconf via chroot, then remount the lowerdir of overlayfs back to read-only mode.

To run omv-mkconf through omv-apply-config, I made a PHP wrapper class \ROPatch\Process (process.inc) and a script (start.inc) to use this class instead of \OMV\System\Process, this script is loaded via php conf.d, using the auto_prepend_file parameter, so this should not break when updating the OMV.

PHP Configuration:

echo auto_prepend_file="/usr/share/php/openmediavault/ro-patch/start.inc" > /etc/php5/cli/conf.d/ro-patch.ini
echo auto_prepend_file="/usr/share/php/openmediavault/ro-patch/start.inc" > /etc/php5/fpm/conf.d/ro-patch.ini

I also created the rwroot script to temporarily remount the file system into read-write mode, which exec bash via chroot. It can be useful for manual configuration through the terminal.

#!/bin/bash
# OMV apply config in read-only mode (C) Andy Chentsov <chentsov@gmail.com>
# put this file in /usr/sbin/, chmod +x /usr/sbin/omv-apply-config
# check arguments
if [ $# -eq 0 ];
then
exit 1
fi
# set exit handler
exit_cleanup() {
# unmount mounts
for f in /mnt/root-ro/{proc,sys}; do
umount -l -n $f
done
# unmount hdds
tmp_hdd_mounts=$(mount -l | grep /mnt/root-ro/srv | cut -d' ' -f3)
for tmp_hdd_mnt_point in $tmp_hdd_mounts; do
umount -n $tmp_hdd_mnt_point
done
# remount read-only again
mount -o remount,ro /mnt/root-ro
}
trap 'exit_cleanup' EXIT HUP INT QUIT PIPE TERM
# remount root rw
mount -o remount,rw /mnt/root-ro
# sync OMV config
rsync /mnt/root-rw/upper/etc/openmediavault/config.xml /mnt/root-ro/etc/openmediavault
# mount special filesystems
mount -t proc proc /mnt/root-ro/proc
mount --bind /sys /mnt/root-ro/sys
# mount hdds
hdd_mounts=$(mount -l | grep /srv | cut -d' ' -f3)
for hdd_mnt_point in $hdd_mounts; do
mount --bind $hdd_mnt_point /mnt/root-ro$hdd_mnt_point
done
# chroot
chroot /mnt/root-ro /usr/bin/env PS1="(rW) \u@\h:\w\$ " /bin/bash --noprofile -l -c "$@"
<?php
// OMV apply read-only patch (C) Andy Chentsov <chentsov@gmail.com>
// put this file in /usr/share/php/openmediavault/ro-patch/
namespace ROPatch;
class Process extends \OMV\System\ProcessOrig {
public function getCommandLine() {
$result = parent::getCommandLine();
if (stripos($result, 'omv-mkconf') !== false) {
$result = 'omv-apply-config "' . str_replace('"', '\\"', $result) . '"';
}
return $result;
}
}
#!/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
#!/bin/bash
# Temporary work in the read and write mode of the file system in the terminal (C) Andy Chentsov <chentsov@gmail.com>
# put this file in /usr/local/bin/, chmod +x /usr/local/bin/rwroot
# remount root rw
mount -o remount,rw /mnt/root-ro
# mount special filesystems
mount -t proc proc /mnt/root-ro/proc
mount --bind /sys /mnt/root-ro/sys
# mount hdds
hdd_mounts=$(mount -l | grep /srv | cut -d' ' -f3)
for hdd_mnt_point in $hdd_mounts; do
#echo "Mount: $hdd_mnt_point -> /mnt/root-ro$hdd_mnt_point"
mount --bind $hdd_mnt_point /mnt/root-ro$hdd_mnt_point
done
# set exit handler
exit_cleanup() {
# unmount mounts
for f in /mnt/root-ro/{proc,sys}; do
umount -l -n $f
done
# unmount hdds
tmp_hdd_mounts=$(mount -l | grep /mnt/root-ro/srv | cut -d' ' -f3)
for tmp_hdd_mnt_point in $tmp_hdd_mounts; do
#echo "Unmount: $tmp_hdd_mnt_point"
umount -n $tmp_hdd_mnt_point
done
# remount read-only again
echo -e "\e[32mChroot left, re-mounting read-only again.\e[39m"
mount -o remount,ro /mnt/root-ro
}
trap 'exit_cleanup' EXIT HUP INT QUIT PIPE TERM
# chroot
echo "Note: /boot is still mounted read-only, remount to read-write if needed."
echo -e "\e[33mYou are now in read-write chroot. Use CTRL+D when done to exit chroot and mount read-only again.\e[39m"
if [ $# -eq 0 ];
then
# Normal shell
chroot /mnt/root-ro /usr/bin/env PS1="(rW) \u@\h:\w\$ " /bin/bash --noprofile -l
else
# Shell with command
chroot /mnt/root-ro /usr/bin/env PS1="(rW) \u@\h:\w\$ " /bin/bash --noprofile -l -c "$@"
fi
<?php
// OMV apply read-only patch (C) Andy Chentsov <chentsov@gmail.com>
// put this file in /usr/share/php/openmediavault/ro-patch/
spl_autoload_register(function($className) {
if (trim($className, '\\') == 'OMV\System\Process') {
include_once '/usr/share/php/openmediavault/ro-patch/process.inc';
class_alias('\ROPatch\Process', '\OMV\System\Process', false);
}
if (trim($className, '\\') == 'OMV\System\ProcessOrig') {
$orig_class = file_get_contents('/usr/share/php/openmediavault/system/process.inc');
$orig_class = str_replace(array('class Process {', '<?php'), array('class ProcessOrig {', ''), $orig_class);
eval($orig_class);
}
}, true, true);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment