天天看点

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

2021年起的 RaspberryPi 官网提供的 RaspiOS 内置了 Overlay 模式,启用或者停用设置在 raspi-config 中进行。具体方法为在 shell 中运行 sudo raspi-config。

#-----------------------------------------------------------------------------------------------------------------------------

复制下面内容,保存为readme.txt,使用时按照本文档说明实施。

脚本用途
============================================================
将树莓派系统在可读写模式和只读模式下转换。


使用场景
============================================================
按照通常的方式,树莓派系统安装于TF/SD卡模式时,系统运行过程中,
可能会频繁的对TF/SD卡做读写操作,容易造成TF/SD卡的损坏。


将系统设定为只读模式后,所有的读写操作均只在内存中进行,不需要
对TF/SD卡进行写,能有效延长TF/SD卡的使用寿命。

只读模式适用于用树莓派做固定控制时使用。


支持环境
============================================================
硬件:RaspberryPi B+
软件:raspbian-jessie
当前脚本只对采用/dev/mmcblk0px方式索引磁盘分区的场景有效,对
采用PARTUUID方式索引磁盘分区时,脚本无效,待后续研究。


脚本组成
============================================================
overlay_enable.py  将系统由读写状态切换至只读状态,重启后生效。
overlay_disable.py将系统由只读状态切换至读写状态,重启后生效。


脚本使用
============================================================
在读写模式下,命令行执行 
    sudo python3 overlay_enable.py
或运行
    chmod +x overlay_enable.py
设置为可执行后执行
    sudo ./overlay_enable.py
将系统由读写状态切换至只读状态,重启后生效。

在只读模式下,命令行执行 
    sudo python3 overlay_disable.py
或运行
    chmod +x overlay_disable.py
设置为可执行后执行
    sudo ./overlay_disable.py
将系统由只读状态切换至读写状态,重启后生效。


只读状态下临时启用可写
============================================================
在命令行执行
    sudo mount -o remount,rw /boot
将使/boot分区进入可写模式,操作完毕运行
    sudo mount -o remount,ro /boot
将/boot分区切换回只读状态。

在命令行执行
    sudo mount -o remount,rw /ro
将使/ro分区进入可写模式,操作完毕运行
    sudo mount -o remount,ro /ro
将/ro分区切换回只读状态。

注:使用本脚本将系统切换进只读模式后,/dev/mmcblk0p2挂载到
/ro,故要使/dev/mmcblk0p2可写,需要对/ro进行重新挂载。
要保存的对/dev/mmcblk0p2中文件的修改,都需要通过对/ro下对应
位置的文件修改来实现。


补充说明
============================================================
Raspbian全系列官方下载地址
https://downloads.raspberrypi.org/raspbian_lite/images/
官方网站放出的最晚一版raspbian-jessie为
2017-07-05-raspbian-jessie
该版本烧写到TF/SD卡后,采用PARTUUID方式进行磁盘分区索引。
为适用本脚本,需要将磁盘索引更改为/dev/mmcblk0px方式,更改后
系统可以正常运行(raspbian-stretch及以后版本貌似不行)。
需要更改的位置有:
/boot/cmdline.txt 中,root=PARTUUID=....-02 更改为 root=/dev/mmcblk0p2
/etc/fstab中,PARTUUID=....-01更改为/dev/mmcblk0p1
PARTUUID=....-02更改为/dev/mmcblk0p2
先完成更改再运行脚本,脚本依赖前面修改的内容。


参考
============================================================
本脚本在
https://blog.csdn.net/zhufu86/article/details/78906046
介绍方法的基础上完成。
           

复制下面内容为 overlay_enable.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Jul 21 15:20:19 2019

@author: farman
"""

import os


def execute(command):
    return os.system(command)


def step_1_disable_swap_files():
    flag = 0
    flag |= execute("sudo dphys-swapfile swapoff")
    flag |= execute("sudo dphys-swapfile uninstall")
    flag |= execute("sudo update-rc.d dphys-swapfile remove")
    return flag


def step_2_update_system():
    flag = 0
    flag |= execute("sudo apt update")
    flag |= execute("sudo apt upgrade -y")
    return flag


def step_3_make_shell_script():
    content = '''#!/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
)"
'''

    with open('/boot/cmdline.txt', 'r') as f:
        line = f.read()
        begin = line.find('root=') + len('root=')
        end   = line.find(' ', begin)
        part = line[begin:end]
    
    content = content.replace("rootDev=/dev/mmcblk0p2", "rootDev="+part)

    with open("/home/pi/overlayroot.sh", 'w') as f:
        f.write(content)
    
    flag = False 
    flag |= execute("sudo mv /home/pi/overlayroot.sh /sbin/overlayroot.sh")
    flag |= execute("sudo chmod +x /sbin/overlayroot.sh")
    
    return flag


def step_4_edit_cmdline():
    with open("/boot/cmdline.txt", "r") as f:
        line = f.read()
        
    tail = "init=/sbin/overlayroot.sh"
    
    if line.find(tail) == -1:
        line = line.rstrip('\n')
        line += ' '
        line += tail
        
        with open("/boot/cmdline.txt", 'w') as f:
            f.write(line)
    
    return


def step_5_edit_fstab():
    lines = []
    
    with open('/etc/fstab', 'r') as f:
        for line in f:
            lines.append(line.rstrip('\n'))
    
    with open('/etc/fstab', 'w') as f:
        for line in lines:
            if line.find('/boot') > -1:
                segs = line.split()
                
                if segs[3].find('ro')  == -1:
                    segs[3] += ',ro'
                
                line = '    '.join(segs)
            
            f.write(line + '\n')

    return    


def step_6_set_prompt():
    with open('/home/pi/.bashrc', 'r') as f:
        content = f.read()
        
    if content.find('overlay') == -1:
        content += '\n'
        content += '''
find_overlay=`mount | grep overlay`

if [ ${#find_overlay} -gt 0 ]; then
  PS1="[OVL] $PS1"
fi
        '''
        with open('/home/pi/.bashrc', 'w') as f:
            f.write(content)
            
    return



if __name__ == '__main__':
    step_1_disable_swap_files()
    #step_2_update_system()
    step_3_make_shell_script()
    step_4_edit_cmdline()
    step_5_edit_fstab()
    step_6_set_prompt()
           

复制下面代码,保存为overlay_disable.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Jul 21 15:20:19 2019

@author: farman
"""
import os


def execute(command):
    return os.system(command)


def step_1_make_partition_writable():
    execute("sudo mount -o remount,rw /boot")
    execute("sudo mount -o remount,rw /ro")
    return


def step_2_edit_cmdline():
    with open("/boot/cmdline.txt", "r") as f:
        line = f.read()
        
    tail = "init=/sbin/overlayroot.sh"
    line = line.replace(tail, '')
    
    with open("/boot/cmdline.txt", 'w') as f:
        f.write(line)
    
    return


def step_3_edit_fstab():
    lines = []

    with open('/ro/etc/fstab', 'r') as f:
        for line in f:
            lines.append(line.rstrip('\n'))

    with open('/ro/etc/fstab', 'w') as f:
        for line in lines:
            if line.find('/boot') > -1:
                segs = line.split(' ')                
                
                while segs.count(''):
                    segs.remove('')

                segs[3] = segs[3].replace(',ro', '')
                line = '    '.join(segs)

            f.write(line + '\n')
    return

if __name__ == '__main__':
    step_1_make_partition_writable()
    step_2_edit_cmdline()
    step_3_edit_fstab()