天天看點

樹莓派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()