[メモ] Raspberry Piで OverlayFS (組み込み向け設定・スクリプト)



## 概要
- `raspberry pi overlayfs` とかで、検索していたら、[Domoticz - Open Source Home Automation System](https://www.domoticz.com)というプロジェクトのWikiに気になる記事が。
- [Setting up overlayFS on Raspberry Pi ](https://www.domoticz.com/wiki/Setting_up_overlayFS_on_Raspberry_Pi)
    - `/boot`と`root`ファイルシステムは通常、読み取り専用(ro)ーに変更.
    - `/var` と`/home` は読み書きできる(rw)オーバーレイファイルシステムに変更.
    - `/etc/init.d/saveoverlays`のinitスクリプトで、システムのシャットダウンと再起動時にSDに同期させる。

- これを使用すると、ほぼ完全にSDカードの書き込みを排除することができます。 読み書きの部分は、RAMベース(=Ramdisk)なので、(Domoticzを)スピードアップします。

\#1 Google翻訳+ちょいと改
\#2 Domoticz: Open Source Home Automation System というのが気になる。。がまたの機会に..
\#3 <font color='red'>再起動前に</font>`sudo service saveoverlays sync`をするのがよいかも。(RAMDisk上のファイルをSD側へ、rsync(なければコピー)してくれる)
\#4 rootfsを`ro`にすると、`apt-get`やら、できなくなる。設定の済んだ、組み込み以外は、あつかいが大変カモ?



## 環境
- Raspberry Pi 2/3 (1でもZeroでもいけるかと)
- `2016-05-27-raspbian-jessie-lite.img`


## 手順
1. 書いてあるとおりに..
2. いつもの+設定など行う (`/home`と`/var`しか、書き込みできなくなるので、そのようにする)
3. こぴぺ

    ```bash
cd /tmp
wget http://hansrune.net/domo/mount_overlay
wget http://hansrune.net/domo/saveoverlays
chmod a+rx saveoverlays mount_overlay
sudo cp mount_overlay /usr/local/bin
sudo cp saveoverlays /etc/init.d/
#
#
# Swapをとめる.
sudo dphys-swapfile swapoff
sudo dphys-swapfile uninstall 
sudo update-rc.d dphys-swapfile disable
#
#
# fake-hwclockサービスは、`/etc/fake-hwclock.data`ファイルを読み書きするので、実態を/var以下に移して、リンクをはる.
# ここでサービスをdisableするが、saveoverlaysサービスで、呼ばれる
sudo service fake-hwclock stop 
sudo mv /etc/fake-hwclock.data /var/log/fake-hwclock.data
sudo ln -s /var/log/fake-hwclock.data /etc/fake-hwclock.data
sudo service fake-hwclock start
sudo update-rc.d fake-hwclock disable
#
# インストール fuse lsof rsync
# lsof: ファイルシステムをリードオンリーに再マウントできないとき、オープンしてるファイルをみつけるのに役立つよ.
sudo apt-get install -y fuse lsof rsync
#
# roを開始するとめんどうになるので、いろんな設定をしといたほうがいいかと。
#
#
#
#サービス開始
sudo systemctl enable saveoverlays.service
```

3. `sudo vi /boot/cmdline.txt`

    ```txt:/boot/cmdline.txt 
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait noswap fastboot ro
```
    - `noswap fastboot ro`を追加

4. `sudo vi /etc/fstab`

    ```txt:/etc/fstab
proc            /proc           proc    defaults          0       0
/dev/mmcblk0p1  /boot           vfat    ro                0       2
/dev/mmcblk0p2  /               ext4    ro,noatime        0       1
mount_overlay   /var            fuse    nofail,defaults   0       0
mount_overlay   /home           fuse    nofail,defaults   0       0
none            /tmp            tmpfs   defaults          0       0
```
    - `/dev/mmcblk0p1`と`/dev/mmcblk0p2` が `ro`になってるよ

5. Overlay関連

    ```txt:こぴぺ
#
#Overlayに使うのディレクトリ
sudo mv /home /home_org
sudo mkdir /home /home_rw 
sudo mv /var /var_org
sudo mkdir /var /var_rw
#
#マウントしてみて、確認. 
sudo mount /home
sudo mount /var
#
mount | grep home ; mount | grep var 
ls -la /home/pi
```


    ```shell-session:こんなかんじ
pi@raspberrypi:~ $ mount | grep home ; mount | grep var 
ramdisk on /home_rw type tmpfs (rw,relatime)
overlay on /home type overlay (rw,relatime,lowerdir=/home_org,upperdir=/home_rw/upper,workdir=/home_rw/work)
ramdisk on /var_rw type tmpfs (rw,relatime)
overlay on /var type overlay (rw,relatime,lowerdir=/var_org,upperdir=/var_rw/upper,workdir=/var_rw/work)
pi@raspberrypi:~ $ ls -la /home/pi
total 20
drwxr-xr-x 2 pi   pi   4096 Jun 14 23:03 .
drwxr-xr-x 1 root root   40 Jun 14 23:28 ..
-rw-r--r-- 1 pi   pi    220 May 27 11:09 .bash_logout
-rw-r--r-- 1 pi   pi   3512 May 27 11:09 .bashrc
-rw-r--r-- 1 pi   pi    675 May 27 11:09 .profile
-rw------- 1 pi   pi     57 Jun 14 23:03 .Xauthority
```

6. `sudo reboot`
7. マウントできてるか確認

    ```shell-session:mount
pi@raspberrypi:~ $ mount
/dev/mmcblk0p2 on / type ext4 (ro,noatime,data=ordered)
.
.
.
fusectl on /sys/fs/fuse/connections type fusectl (rw,relatime)
none on /tmp type tmpfs (rw,relatime)
ramdisk on /home_rw type tmpfs (rw,relatime)
ramdisk on /var_rw type tmpfs (rw,relatime)
/dev/mmcblk0p1 on /boot type vfat (ro,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro)
overlay on /var type overlay (rw,relatime,lowerdir=/var_org,upperdir=/var_rw/upper,workdir=/var_rw/work)
overlay on /home type overlay (rw,relatime,lowerdir=/home_org,upperdir=/home_rw/upper,workdir=/home_rw/work)
pi@raspberrypi:~ $ 
```
    - 見所
<br>`/dev/mmcblk0p2 ... (ro, ...)` : リードオンリー
<br>`/dev/mmcblk0p1 ... (ro, ...)` : リードオンリー
<br>`/home_rw`と`/var_rw` : RAMディスク
<br>`/home`と`/var` : lowerdirをベースにupperdirを重ねる(`lowerdir=/XXX_org,upperdir=/XXX_rw/upper,workdir=/XXX_rw/work`で、overlay)

### システムメンテナンスのためのスクリプト
- `systemd`を使っているとうまく動かないので、<font color='red'>[↓のコメント](http://qiita.com/mt08/items/24510d9845b77ef28d8b#comment-ad8bb42722e463b64e99)の変更したスクリプトを使用すること!</font>

```bash:sysmaint.shというファイル名にしときます.
#!/bin/bash

# remount root rw
mount -o remount,rw /

# prapare target paths
mkdir -p /chroot
mkdir -p /chroot/{bin,boot,dev,etc,home,lib,opt,proc,root,run,sbin,sys,tmp,usr,var}

# mount special filesystems
mount -t proc proc /chroot/proc
mount --rbind /sys /chroot/sys
mount --rbind /dev /chroot/dev

# bind rw directories
for f in {home,var}; do mount --rbind /${f}_org /chroot/$f; done

# bind remaining directories
for f in {bin,boot,etc,lib,opt,root,run,sbin,tmp,usr}; do mount --rbind /$f /chroot/$f; done

# 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"
chroot /chroot /usr/bin/env PS1="(rw) \u@\h:\w\$ " /bin/bash --noprofile -l

# unmount mounts
for f in /chroot/{bin,boot,dev,etc,home,lib,opt,proc,root,run,sbin,sys,tmp,usr,var}; do
umount -l $f
done

sleep 1

# remount read-only again
echo -e "\e[32mChroot left, re-mounting read-only again.\e[39m"
mount -o remount,ro /
```

- `sudo ./sysmaint.sh`
- => 実行すると`chroot`つかって、ファイルの読み書きができるようになる(\# `/etc/fstab`で、rwにして再起動とか..)

    ```shell-session:例
pi@raspberrypi:~$ sudo ./sysmaint.sh
Note: /boot is still mounted read-only, remount to read-write if needed.
You are now in read-write chroot. Use CTRL+D when done to exit chroot and mount read-only again.
(rw) root@raspberrypi:/$ vi /etc/fstab 
...
(rw) root@raspberrypi:/$ logout
Chroot left, re-mounting read-only again.
pi@raspberrypi:~$
```


### もし、読み取り専用に再マウントできなかったら

```
sudo service saveoverlays sync
sudo reboot
```
### SDカードにシンクできてるかな？
`/var/log/saveoverlays.log` にログってる。



### その他

### `mount_overlay`

```bash:hansrune.net/domo/mount_overlay
#!/bin/sh
DIR="$1"
[ -z "${DIR}" ] && exit 1 
#if ! grep -q overlay /proc/filesystems
#then
#    echo "Filesystem overlay is not available. You need a kernel update: apt-get update && apt-get upgrade"  >&2
#    exit 2
#fi
if [ ! -d "${DIR}_org" ]
then
    echo "${DIR}_org does not exist" >&2
    exit 1
fi
if [ ! -d "${DIR}_rw" ]
then
    echo "${DIR}_rw does not exist" >&2
    exit 1
fi
#
# ro must be the first mount option for root .....
#
ROOT_MOUNT=$( grep -v "^#" /etc/fstab | awk '$2=="/" { print substr($4,1,2) }' )
if [ "$ROOT_MOUNT" != "ro" ]; then
    /bin/mount --bind ${DIR}_org ${DIR}
else
    /bin/mount -t tmpfs ramdisk ${DIR}_rw
    /bin/mkdir ${DIR}_rw/upper
    /bin/mkdir ${DIR}_rw/work
    OPTS="-o lowerdir=${DIR}_org,upperdir=${DIR}_rw/upper,workdir=${DIR}_rw/work"
    /bin/mount -t overlay ${OPTS} overlay ${DIR}
fi
```

### `saveoverlays`

```bash:hansrune.net/domo/saveoverlays
#! /bin/sh
### BEGIN INIT INFO
# Provides:          saveoverlays
# Required-Start:    $local_fs $time
# Required-Stop:     $local_fs $syslog $time
# Default-Start:     S
# Default-Stop:      0 6
# Short-Description: Save logs and files from overlay mounts
# Description:       Save logs and files from overlay mounts
### END INIT INFO

# Do NOT "set -e"
#
# Testing hints:
#	sudo mount -o remount,ro / 
#	sudo env INIT_VERBOSE=yes /etc/init.d/saveoverlays stop
#	cat /var/log/saveoverlays.log  

PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
DESC="overlay filesystem sync"
NAME=saveoverlays

if [ -f "/etc/default/$NAME" ]; then
    . "/etc/default/$NAME"
fi
TMPLOG=/tmp/$NAME.log
LOGFILE=/var_org/log/$NAME.log

SYNCDIRS=${SYNCDIRS:-$( mount | awk '/^overlay/ { print $3 ":"  $3 "_org" }' )}
SYNCEXCLUDES=${SYNCEXCLUDES:-'--exclude .unionfs* --exclude .fuse_hidden* --exclude *.leases --exclude stats'}
SYNCFLAGS=${SYNCFLAGS:-"-avH --inplace --delete"}

# Check if we are running with read-only root
ROROOT=$( mount | egrep '^/dev/.*on / .*ro,' )
DOSYNC=${FORCESYNC:-"$ROROOT"}

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
	if [ -n "${ROROOT}" ]
	then
		log_action_msg "Read-only root active"
	else
		log_action_msg "Read-only root inactive"
	fi
	if date '+%Y' | grep -q 1970
	then
		log_action_msg "Clock is not set. Trying fake-hwclock"
		fake-hwclock load
	fi
}

#
# Function that syncs the files
#
do_sync()
{
        RETVAL=0
        #
        # If we run with overlayfs, try to sync the dirs
        #
	if [ -z "${ROROOT}" ] || mount -o remount,rw / >> ${TMPLOG} 2>&1
	then
		echo "----------------------------" >> ${TMPLOG}
		echo "$NAME sync started at `date`" >> ${TMPLOG}
		for DIR in ${SYNCDIRS}
		do
			SOURCE="${DIR%%:*}"
			DEST="${DIR##*:}"
			log_action_msg "Syncing ${SOURCE} to ${DEST} ..."
			echo "----"                                      >> ${TMPLOG}
			echo "$NAME sync ${SOURCE} to ${DEST} with options ${SYNCFLAGS} ${SYNCEXCLUDES} at `date`" >> ${TMPLOG}
			if [ -d "${SOURCE}" -a -d "${DEST}" ] 
			then
				rsync ${SYNCFLAGS} ${SYNCEXCLUDES} ${SOURCE}/ ${DEST}/ >> ${TMPLOG} 2>&1
			elif [ -f "${SOURCE}" ]
			then
				cp -vp ${SOURCE} ${DEST} >> ${TMPLOG} 2>&1
			else
				log_action_msg "Skipping this step: ${SOURCE} or ${DEST} not available"
			fi
		done
		cat ${TMPLOG} >> ${LOGFILE}
		if [ -w /etc/fake-hwclock.data ]
		then
			log_action_msg "Saving fake-hwclock"
			fake-hwclock save
		fi
		log_action_msg "Sync changes to disk"
		sync; sync; sync
		#
		# return to read-only only if that is where we started
		#
		if [ -n  "${ROROOT}" ]
		then
			log_action_msg "Remount read-only"
			mount -o remount,ro /
		fi
	else
		log_action_msg "Remounting root as writeable failed!"
		RETVAL=2
	fi
        return "$RETVAL"
}

#
# Function that stops the daemon/service
#
do_stop()
{
        #
        # If we run with overlayfs, try to sync the dirs
        #
        if [ -n "${DOSYNC}" ]
        then
		do_sync;
		return $?
	else
		log_action_msg "Root is not read-only. No action"
		return 0
        fi
}


case "$1" in
  start)
        log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) log_end_msg 0 ;;
                2)   log_end_msg 1 ;;
        esac
        ;;
  stop)
        log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) log_end_msg 0 ;;
                2)   log_end_msg 1 ;;
        esac
        ;;
  sync)
        log_daemon_msg "Syncing $DESC" "$NAME"
        do_sync
        case "$?" in
                0|1) log_end_msg 0 ;;
                2)   log_end_msg 1 ;;
        esac
        ;;
  status)
        ;;
  restart)
        ;;
  *)
        echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2
        exit 3
        ;;
esac

:
```