使用overlayfs打造一个只读的不怕意外关机的树莓派Raspberry Pi

树莓派的本领就不多说了。但是在树莓派的应用场合,关机的时候还是显得尴尬,先不说执行 sudo halt 要么需要ssh上去,要么需要有键盘和显示器,更不要说,有的场景可能连网络和显示器都没有,真正的 headless。 但是如果不执行sudo halt直接关电源,那么有很大的概率会损坏SD卡上的文件系统,甚至损坏SD卡。

overlayfs是linux系统下的一种影子文件系统,它可以把真正的存储文件系统作为只读挂载,而把所有文件改动都存放在RAM中,关机或者重启就失效,真正的保护了存储文件系统。 下面就说一下我在树莓派中实施overlayfs的方法,其实很简单。

我参考了这个帖子中介绍的方法:

https://www.raspberrypi.org/forums/viewtopic.php?t=173063#p1151405

  1. 首先,禁用交换空间,毕竟树莓派里的交换空间也是占用RAM(tempfs)的,所以如果使用了overlayfs后,交换空间就显得没有意义了。

执行如下3条命令:

sudo dphys-swapfile swapoff
sudo dphys-swapfile uninstall
sudo update-rc.d dphys-swapfile remove

  1. 更新系统,保证系统为最新状态

sudo apt-get update
sudo apt-get upgrade

  1. 保存如下脚本为 overlayRoot.sh

注(2020-11-22):如下脚本已过时,最新脚本请前往链接【 http://wiki.psuter.ch/doku.php?id=solve_raspbian_sd_card_corruption_issues_with_read-only_mounted_root_partition#the_script 】

!/bin/sh

Read-only Root-FS for Raspian using overlayfs

Version 1.0

#

Created 2017 by Pascal Suter @ DALCO AG, Switzerland

to work on Raspian as custom init script

(raspbian does not use an initramfs on boot)

#

Modified 2017-Apr-21 by Tony McBeardsley

#

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/.

#
#

Tested with Raspbian mini, 2017-01-11

#

This script will mount the root filesystem read-only and overlay it with a temporary tempfs

which is read-write mounted. This is done using the overlayFS which is part of the linux kernel

since version 3.18.

when this script is in use, all changes made to anywhere in the root filesystem mount will be lost

upon reboot of the system. The SD card will only be accessed as read-only drive, which significantly

helps to prolong its life and prevent filesystem coruption in environments where the system is usually

not shut down properly

#

Install:

copy this script to /sbin/overlayRoot.sh and add “init=/sbin/overlayRoot.sh” to the cmdline.txt

file in the raspbian image’s boot partition.

I strongly recommend to disable swapping before using this. it will work with swap but that just does

not make sens as the swap file will be stored in the tempfs which again resides in the ram.

run these commands on the booted raspberry pi BEFORE you set the init=/sbin/overlayRoot.sh boot option:

sudo dphys-swapfile swapoff

sudo dphys-swapfile uninstall

sudo update-rc.d dphys-swapfile remove

#

To install software, run upgrades and do other changes to the raspberry setup, simply remove the init=

entry from the cmdline.txt file and reboot, make the changes, add the init= entry and reboot once more.

fail(){
echo -e “$1”
/bin/bash
}

Load overlay module

modprobe overlay
if [ $? -ne 0 ]; then
fail “ERROR: missing overlay kernel module”
fi

Mount /proc

mount -t proc proc /proc
if [ $? -ne 0 ]; then
fail “ERROR: could not mount proc”
fi

Create a writable fs on /mnt to then create our mountpoints

mount -t tmpfs inittemp /mnt
if [ $? -ne 0 ]; then
fail “ERROR: could not create a temporary filesystem to mount the base filesystems for overlayfs”
fi

Mount a tmpfs under /mnt/rw

mkdir /mnt/rw
mount -t tmpfs root-rw /mnt/rw
if [ $? -ne 0 ]; then
fail “ERROR: could not create tempfs for upper filesystem”
fi

Identify root fs device, PARTUUID, mount options and fs type

rootDev=blkid -o list | awk '$3 == "/" {print $1}'

Changed here(point to / ) in case the cmd above doesn’t work # By ChenYang 20171122

rootDev=/dev/mmcblk0p2
rootPARTUUID=awk '$2 == "/" {print $1}' /etc/fstab
rootMountOpt=awk '$2 == "/" {print $4}' /etc/fstab
rootFsType=awk '$2 == "/" {print $3}' /etc/fstab

Mount original root filesystem readonly under /mnt/lower

mkdir /mnt/lower
mount -t ${rootFsType} -o ${rootMountOpt},ro ${rootDev} /mnt/lower
if [ $? -ne 0 ]; then
fail “ERROR: could not ro-mount original root partition”
fi

Mount the overlay filesystem

mkdir /mnt/rw/upper
mkdir /mnt/rw/work
mkdir /mnt/newroot
mount -t overlay -o lowerdir=/mnt/lower,upperdir=/mnt/rw/upper,workdir=/mnt/rw/work overlayfs-root /mnt/newroot
if [ $? -ne 0 ]; then
fail “ERROR: could not mount overlayFS”
fi

Create mountpoints inside the new root filesystem-overlay

mkdir /mnt/newroot/ro
mkdir /mnt/newroot/rw

Remove root mount from fstab (this is already a non-permanent modification)

grep -v “$rootPARTUUID” /mnt/lower/etc/fstab > /mnt/newroot/etc/fstab
echo “#the original root mount has been removed by overlayRoot.sh” >> /mnt/newroot/etc/fstab
echo “#this is only a temporary modification, the original fstab” >> /mnt/newroot/etc/fstab
echo “#stored on the disk can be found in /ro/etc/fstab” >> /mnt/newroot/etc/fstab

Change to the new overlay root

cd /mnt/newroot
pivot_root . mnt
exec chroot . sh -c “$(cat <<END

# Move ro and rw mounts to the new root
mount --move /mnt/mnt/lower/ /ro
if [ $? -ne 0 ]; then
    echo "ERROR: could not move ro-root into newroot"
    /bin/bash
fi
mount --move /mnt/mnt/rw /rw
if [ $? -ne 0 ]; then
    echo "ERROR: could not move tempfs rw mount into newroot"
    /bin/bash
fi

# Unmount unneeded mounts so we can unmout the old readonly root
umount /mnt/mnt
umount /mnt/proc
umount /mnt/dev
umount /mnt

# Continue with regular init
exec /sbin/init

END
)”

注意,我的脚本和帖子里面的有些不同,我把

rootDev=blkid -o list | awk '$3 == "/" {print $1}'

改成了

rootDev=/dev/mmcblk0p2

不改的话,我这里overlayRoot.sh会运行失败

(20180503更新)另外需要注意,如果在Windows下编辑overlayRoot.sh文件,需要保存为Unix文本文档格式,即行结束符为单个LF字符,而不是Windows风格的CR+LR两个字符。否则你可能会看到如下错误:

—[ end Kernel panic – not syncing: Requested init /sbin/overlayRoot.sh failed (error -2).

  1. 将脚本拷贝到 /sbin/overlayRoot.sh

sudo cp overlayRoot.sh /sbin/overlayRoot.sh
sudo chmod a+x /sbin/overlayRoot.sh

  1. 修改 cmdline.txt 文件,在行尾加上 “init=/sbin/overlayRoot.sh”,如下是我的 /boot/cmdline.txt 文件的内容

pi@raspberrypi:~ $ cat /boot/cmdline.txt
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=0e82c2e4-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait init=/sbin/overlayRoot.sh

  1. 为了保证整个SD卡都是只读,将 /boot 分区也修改为只读, 修改 fstab 文件,把/boot 对应的行改为ro,我的/etc/fstab文件内容如下

pi@raspberrypi:~ $ cat /etc/fstab
proc /proc proc defaults 0 0
PARTUUID=0e82c2e4-01 /boot vfat defaults,ro 0 2
PARTUUID=0e82c2e4-02 / ext4 defaults,noatime 0 1

  1. 到这里 sudo reboot 重启,重启后,文件系统就处于影子系统的保护之下了,所有对于文件系统的改动,在重启后都将恢复原状。 用mount命令可以确认overlayfs的正常工作。

root-rw on /rw type tmpfs (rw,relatime)
/dev/mmcblk0p2 on /ro type ext4 (ro,noatime,data=ordered)
overlayfs-root on / type overlay (rw,relatime,lowerdir=/mnt/lower,upperdir=/mnt/rw/upper,workdir=/mnt/rw/work)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
proc on /proc type proc (rw,relatime)
devtmpfs on /dev type devtmpfs (rw,nosuid,size=470160k,nr_inodes=117540,mode=755)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /run type tmpfs (rw,nosuid,nodev,mode=755)
tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=25,pgrp=1,timeout=0,minproto=5,maxproto=5,direct)
debugfs on /sys/kernel/debug type debugfs (rw,relatime)
sunrpc on /run/rpc_pipefs type rpc_pipefs (rw,relatime)
mqueue on /dev/mqueue type mqueue (rw,relatime)
configfs on /sys/kernel/config type configfs (rw,relatime)
/dev/mmcblk0p1 on /boot type vfat (ro,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro)
tmpfs on /run/user/1000 type tmpfs (rw,nosuid,nodev,relatime,size=94952k,mode=700,uid=1000,gid=1000)

原来的根文件系统 /dev/mmcblk0p2 改为挂载在 /ro ,并且是只读;/boot 也挂载为只读;而 / 的 type 变成了overlay。

好了,直接拔电试试看吧。事实上你可以反复尝试不正常关机,都不会有问题。

  1. 以后如果想禁用overlayfs,可以修改 cmdline.txt 把 “init=/sbin/overlayRoot.sh” 删掉,重启即可。 当然修改前要先把 /boot remount 成可写状态。

更新系统等操作都需要先禁用overlayfs后再执行。

  1. 如果临时想修改原 /boot 以及根文件系统的内容可以如下命令remount

要remount /boot为可写

pi@raspberrypi:~ $ sudo mount -o remount,rw /boot

原来的根文件系统在overlayfs下是挂载在 /ro 的,要remount的话

pi@raspberrypi:~ $ sudo mount -o remount,rw /ro

注意,remount文件系统为可写后,要重启和关机需要使用sudo reboot或者sudo halt,不可以强行非正常关机,以免损坏文件系统。

PS. 网上有人报告,如果在不接显示器的情况下(即 headless),如上脚本会运行失败,但是我不接显示器时并没有遇到此问题 。

PS2. 网上有另外一个脚本,链接如下

https://gist.githubusercontent.com/niun/34c945d70753fc9e2cc7/raw/3d60338cd8d8daf740692f426a5a1ec17839d613/root-ro

使用了 update-initramfs 的方式,我在官方系统中可以使用,但是在修改了内核的系统中,运行失败。

【20180717后记】

— 如果开启了overlay以后,root分区的所有变动重启后都会丢失,如果要保存数据,可以缩小root分区后另外建立一个分区,使用FAT32,并mount为rw, sync, flush,用来保存数据,另外最好使用f2fs分区格式,据说f2fs就是针对SD卡之类的存储优化设计的。当然保存数据的频率不要太高,否则异常关机仍有可能会损坏SD卡,就失去使用overlay的意义了。 如果保存数据的频率很高,比如log,可以考虑保存到外接U盘。

— 也有人把 /boot 挂载为rw并临时存放数据,我认为那是不可取的,因为 /boot 里面存放的是启动设置信息以及内核等。

— 网上也有人修改脚本,(https://github.com/jacobalberty/root-ro),使得可以短路某GPIO后启动系统就可写,断开后启动就只读。这是个好主意,有空研究一下。

【20180718更新】 另写了一篇来说明上面这一条的方法。 https://blog.csdn.net/zhufu86/article/details/81100710

【20180720更新】 为了方便识别当前系统是不是在overlayfs模式下,我在 .bashrc 的结尾加上如下几行(当然必须在非overlayfs下修改,否则修改内容不会保存),这样如果是在overlayfs模式下,命令提示行的前面会加上“[OVL]”

find_overlay=mount | grep overlay

if [ ${#find_overlay} -gt 0 ]; then
PS1=”[OVL] $PS1″
fi
【2020-11-22更新】最近留意到raspi-config里面已经有了打开Overlay FS的功能,但是我不是很清楚其使用的方案,大概了解了一下,似乎需要动内核相关文件(如果我的认知有误请指正),这个是我不喜欢的。因为有时如果使用定制化内核,就可能和这种方案有冲突。 同时,我发现本文中方案的原作者已经更新了脚本 overlayRoot.sh ,请前往链接【 http://wiki.psuter.ch/doku.php?id=solve_raspbian_sd_card_corruption_issues_with_read-only_mounted_root_partition 】,他解决了之前自动搜索fstab文件中相关分区信息失败的问题。

https://blog.csdn.net/farmanlinuxer/article/details/96880345

树莓派RaspberryPiB+Raspbian-jessie制作只读系统的python3脚本