Rapberry Pi 初期設定の半自動化

###### 2021/12/13 追記
[シェルスクリプト版](https://qiita.com/god19/items/cbfb04b4f6333009b633 "Rapberry Pi 初期設定の半自動化 シェルスクリプト版 - Qiita") を投稿したのでそちらも参照。

###### 2022/02/19 追記
64 bit Raspberry Pi OS Lite（CUI環境）で`watchdog`動作確認として`:(){ :|:& };:`を実行すると単なる無限ループに陥るだけでハングアップしてくれないことが発覚。要注意。

## 前文
いよいよ Raspberry Pi OS でも Bullseye版が公開された。
[Bullseye – the new version of Raspberry Pi OS](https://www.raspberrypi.com/news/raspberry-pi-os-debian-bullseye/ "Bullseye - the new version of Raspberry Pi OS - Raspberry Pi")

[公式サイト](https://www.raspberrypi.com/software/operating-systems/#raspberry-pi-desktop "Operating system images – Raspberry Pi") での告知はないけど、64bit版も Bullseye版が同時公開。
[Raspberry Pi OS with desktop(64bit)2021-11-08](https://downloads.raspberrypi.org/raspios_arm64/images/raspios_arm64-2021-11-08/ "raspios_arm64-2021-11-08")
[Raspberry Pi OS Lite(64bit)2021-11-08](https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2021-11-08/ "raspios_lite_arm64-2021-11-08")
公式フォーラムでの報告によれば大分安定してきたとの声が多い。64bit版を試してみるのもアリかと。

さて、新規 microSD にイメージを書き込み心機一転、といきたいところだけど。
初期設定が地味に面倒。っていうか前回の初期設定内容なんて覚えていないし。
ということで [過去投稿](https://qiita.com/god19/items/c1cefcd71e8bae9b986e "Busterヘッドレスセットアップ - Qiita") を確認。大筋はこれで大丈夫だった。

しかし、初回起動時の設定以降の各種設定って面倒だよな。
`sudo apt install` の類はコピペ実行で良いけど、設定ファイルの編集がねぇ。いい加減飽きたよ。
全自動化とまでいかなくても、半自動化したい。コピペ実行で済ませたい。それが本投稿の目的。

## 前提
以下の初回起動時の設定までは行っているものとする。

### 1. イメージを microSDカードに書き込み

### 2. bootドライブに sshファイルを、無線LAN 使用の場合は wpa_supplicant.conf も作成

### 3. bootドライブ の cmdline.txtに以下追記(VNCが遅い問題対応)
`video=HDMI-A-1:1920x1080@60D`
参考URL：
[Re: Bullseye vncserver is very slow without display](https://forums.raspberrypi.com/viewtopic.php?p=1935714#p1935711 "Re: Bullseye vncserver is very slow without display")
個人的には `video=HDMI-A-1:1280x720@60D` 位の解像度が丁度いいかな。

### 4. microSDカードを Pi にセットし起動

### 5. ターミナル(TeraTerm等)から raspberrypi.local で SSH 接続

### 6. ターミナル : sudo raspi-config で VNC 有効化
`3 Interface Options` -> `P3 VNC`

### 7. ターミナルから `ip a` で割り当てられている IPアドレス確認し、VNC接続

### 8. VNC : 初期設定ウィザード(piwiz)が起動しているので設定

### 9. VNC : Raspberry Pi Config で Hostname 等設定、再起動

`3.` 以外は [過去投稿](https://qiita.com/god19/items/c1cefcd71e8bae9b986e "Busterヘッドレスセットアップ - Qiita") 参考のこと。

## 半自動化コード
御託はいいからコードをってことで以下。64bit（desktop）環境で検証したけど、32bit環境でも同様に動くと思われ。
<details><summary>クリックで表示</summary><div>

```shell
###############################
# アップデート
# まとめて実行
###############################
sudo apt update -y && sudo apt full-upgrade -y && \
sudo apt autoremove -y && sudo apt clean -y && sudo apt autoclean -y

###############################
# IPアドレス固定
# 各 IPアドレスは書き換えること
###############################
# まとめて実行
dest_file=/etc/dhcpcd.conf
add_str="static ip_address=192.168.xx.xxx/24"
if ! grep -q "$add_str" $dest_file
then
sudo cat <<EOL >> $dest_file
interface wlan0
$add_str
static routers=192.168.xx.1
static domain_name_servers=192.168.xx.1
EOL
fi

# 追記内容確認
tail $dest_file

# 再起動
sudo reboot

###############################
# Swap領域無効化
###############################
sudo swapoff --all && sudo apt purge -y --auto-remove dphys-swapfile && sudo rm -fr /var/swap

# まとめて実行
dest_file=/etc/fstab
add_str1="tmpfs           /tmp            tmpfs   defaults,size=32m,noatime,mode=1777  0       0"
add_str2="tmpfs           /var/tmp        tmpfs   defaults,size=16m,noatime,mode=1777  0       0"
if ! grep -q "$add_str1" $dest_file
then
cat <<EOL | sudo tee -a $dest_file
$add_str1
$add_str2
EOL
fi

# 追記内容確認
cat $dest_file

# 再起動
sudo reboot

###############################
# rsyslogログ出力抑制
# まとめて実行
###############################
sudo cp /etc/rsyslog.conf /etc/rsyslog.conf_bak_`date "+%Y%m%d_%H%M%S"` && \
sudo sed -i \
-e "s/^\(daemon\.\*"$'\t'".*$\)/#\1/" \
-e "s/^\(kern\.\*"$'\t'".*$\)/#\1/" \
-e "s/^\(lpr\.\*"$'\t'".*$\)/#\1/" \
-e "s/^\(mail\.\*"$'\t'".*$\)/#\1/" \
-e "s/^\(user\.\*"$'\t'".*$\)/#\1/" \
-e "s/^\(mail\.info"$'\t'".*$\)/#\1/" \
-e "s/^\(mail\.warn"$'\t'".*$\)/#\1/" \
-e "s/^\(mail\.err"$'\t'".*$\)/#\1/" \
/etc/rsyslog.conf && \
sudo sed -i -z -e \
"s/\(\*\.=debug;\\\\\).*\("$'\t'"auth,authpriv.none;\\\\\).*\("$'\t'"mail\.none.*-\/var\/log\/debug\)/#\1\n#\2\n#\3/" \
/etc/rsyslog.conf && \
sudo systemctl restart rsyslog

###############################
# heartbeat設定
###############################
# まとめて実行
sudo cp /boot/config.txt /boot/config.txt_bak_`date "+%Y%m%d_%H%M%S"` && \
dest_file=/boot/config.txt
if ! grep -q "dtparam=pwr_led_trigger=heartbeat" $dest_file
then
cat <<EOL | sudo tee -a $dest_file
# turn power LED into heartbeat
dtparam=pwr_led_trigger=heartbeat
#
EOL
fi

# 追記内容確認
tail $dest_file

# 再起動
sudo reboot

###############################
-- Watchdog Timer設定
-- 小分けして実行
###############################
sudo apt install watchdog -y

# まとめて実行
dest_file=/lib/systemd/system/watchdog.service
if ! grep -q "WantedBy=Multi-user.target" $dest_file
then
    sudo sed -i -e "s/^\(WantedBy=default.target$\)/#\1/" $dest_file
    cat <<EOL | sudo tee -a $dest_file
WantedBy=Multi-user.target
EOL
fi

# 自動起動、モジュール追加
sudo update-rc.d watchdog enable && sudo modprobe bcm2835_wdt

# オリジナルを保存
sudo cp /etc/watchdog.conf /etc/watchdog.conf_bak_`date "+%Y%m%d_%H%M%S"`

# まとめて実行
dest_file=/etc/watchdog.conf
if ! grep -q "watchdog-timeout = 10" $dest_file
then
    sudo sed -i -e "s/^#\(max-load-1.*= 24$\)/\1/" $dest_file
    sudo sed -i -e "s/^#\(watchdog-device.*= \/dev\/watchdog$\)/\1/" $dest_file
    cat <<EOL | sudo tee -a $dest_file
watchdog-timeout = 10
EOL
fi

# 再起動
sudo reboot

# 再起動後ハングアップテスト
:(){ :|:& };:

###############################
# 複数AP対応 WiFi設定
# 各 ssid、psk は書き換えること
# まとめて実行
###############################
sudo cp /etc/wpa_supplicant/wpa_supplicant.conf /etc/wpa_supplicant/wpa_supplicant.conf_bak_`date "+%Y%m%d_%H%M%S"` && \
cat <<EOL | sudo tee /etc/wpa_supplicant/wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=JP

network={
        ssid="xxx"
        psk="xxx"
        priority=1
}

network={
        ssid="xxx"
        psk="xxx"
        priority=2
}

network={
        ssid="xxx"
        psk="xxx"
        priority=3
}
EOL

###############################
# 各種インストール
# 必要なツールに書き換えること
# まとめて実行
###############################
sudo apt install screen -y && \
sudo apt install kodi -y && \
sudo apt install wavemon -y && \
sudo apt install iperf3  -y
```
</div></details>
## 半自動化コード使用方法
コピペでターミナルから実行を想定。
各ブロック単位で行うが、必要に応じて小分けして実行。
例えば最初の `IPアドレス固定`項は「# まとめて実行」以下の 11行をコピペ実行。エラーが発生しないことを目視で確認。
次に「# 追記内容確認」の次の `tail $dest_file` で追記内容を目視で確認。
そして `sudo reboot` で再起動。

`sudo apt install` でのインストールから始まる項はまずその一行だけコピペで実行、正常にインストールされたことを目視で確認。
続いて設定ファイル編集コマンドを実行、が分かりやすくていいかな。

複数行に渡るコマンドはまとめて実行すること。
また、`複数AP対応 WiFi設定`項は空行も含めてヒアドキュメントなので注意。まとめて実行すること。

それと以下の項は環境に依存するので、各自の環境に書き換えて実行すること。

- IPアドレス固定
- 複数AP対応 WiFi設定

以下の項はそのままで動く筈。

- アップデート
- Swap領域無効化
- rsyslogログ出力抑制
- heartbeat設定
- Watchdog Timer設定

また、必要なツールは人それぞれなので、以下の項は自分が常用しているツールに置き換えて欲しい。

- 各種インストール

## ファイル編集等のコマンドについて
Linuxコマンドに通じている方は読み飛ばして構わない。
ファイル編集等のコマンドの説明。

### ファイルに一行追記
```shell
cat <追記内容> >> <追記ファイル>
```

### ファイルに複数行追記
上記コマンドの連投でも可能だけど冗長なので以下のようにする。

```shell
cat <<EOL >> <追記ファイル>
<追記行1>
<追記行2>
<追記行3>
EOL
```
追加行はヒアドキュメントなので空行があってもそのまま追記される。

しかし、root権限のファイルは `sudo` を使用しても書き込みに失敗する場合が。ちょっと悩んだけど、Google先生のご神託によれば、`tee`コマンドを使えとのこと。
そこで最初の一行は以下となる。

```shell
cat <<EOL | sudo tee -a <追記ファイル>
```

### ファイル編集
よくある、
`aaa=bbb` 行は、先頭にコメント `#` を挿入
の類は以下。

```shell
sed -i -e "s/^\(aaa=bbb$\)/#\1/" <編集ファイル>
```
`-i`スイッチは引数に指定したファイルを直接編集。
`-e`スイッチは、それに続く置換定義を実行する。
先頭を表す `^` を明示的に指定すれば、再実行しても `#` が重複して挿入されることはない。

ただ、`sed`コマンドは行単位が基本なので、複数行を扱うとなると急に難易度が上がる。
今回ちょっと悩んだ。（「rsyslogログ出力抑制」の最後の方ね）
`-z`スイッチを指定すれば複数行の置換になるけど、クセが強くて試行錯誤を強いられた。
他の方法を考えた方が良いかも。

### 追記の判定
初期設定を想定しているので、追記は決め打ちでも大丈夫かもしれないけど、再実行しても問題ないよう、追記済みであるかの判定は以下。

```shell
if ! grep -q "<追記内容>" <追記ファイル>
```
`-q`スイッチで `<追記内容>` が `<追記ファイル>` に存在するかの判定となり、存在すれば `真` となる。（if文が実行される）
今回は `存在しない場合` としたいので、 `!` 指定で真偽値を反転している。

### ファイルバックアップ
極力編集前のファイルは以下の形式（書式）でバックアップを取っている。

```shell
<オリジナルファイル名>_bak_`date "+%Y%m%d_%H%M%S"`
```
ここら辺は好みもあるので自分の好きな形式に改編して欲しい。

## 使用上の注意
っていうかシェルスクリプト化できないのかな、というのもあるけど、再起動が随所に必要なので、やってできないことはないかもしれないけど、急に難易度が高くなる。
全自動となると、エラー判定やらログ出力も必要だろうし。
今回のようなコピペで実行の半自動化が落としどころと思われ。そう考えると Windows のインストールやアップデートで再起動を繰り返しながら自動実行されるのって実は良くできているんだな、とも。

それとこれはテンプレートというか叩き台という位置付け。
入れるツールは人それぞれ。`Watchdog Timer` なんて不要って人は削ればいいし、各自必要なものを追加して欲しい。掲載したコードを参考にすれば他の設定ファイルの更新も容易く書けると思う。
コマンド化してテキストに残しておけば後で楽になる。
`sed`コマンドで置換した後は変更内容を確認したい、であれば、`diff`コマンドでの比較処理を追加するなり、改良方法はいくらでもある。「俺流」の初期設定コマンド群を構築して欲しい。


今回の投稿が皆様のお役に立てれば幸い。良い Raspberry Pi Life を。