Rapberry Pi 初期設定の半自動化 シェルスクリプト版

## 前文
[前回投稿](https://qiita.com/god19/items/68103e3755383e531d4c "Rapberry Pi 初期設定の半自動化 - Qiita") の続き。
一括実行は厳しいだろうけど、各手順を関数に小分けして手動実行、なら可能かなとシェルスクリプト化してみた。

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

## 前提
[初回起動時の設定までは行っているものとする。](https://qiita.com/god19/items/68103e3755383e531d4c#%E5%89%8D%E6%8F%90 "Rapberry Pi 初期設定の半自動化 - Qiita")

そうそう、前回書き忘れたけど、Bullssys版 PiOS公開に合わせ1.8GHzのススメが公式から発表された。
[Bullseye bonus: 1.8GHz Raspberry Pi 4](https://www.raspberrypi.com/news/bullseye-bonus-1-8ghz-raspberry-pi-4/ "Bullseye bonus_ 1.8GHz Raspberry Pi 4 - Raspberry Pi")

クロックアップなんて元々出来たのでは、と思ったら
> 最新のファームウェア（Bullseye のイメージに含まれています）は、特定のオーバークロックに適用するのに最適な電圧を見つけるために最善を尽くします。

とのことらしい。
日々進化、改良が加えられているんだね。
対応方法は簡単で `config.txt` に以下を追記するだけ。

```shell
arm_freq=1800
```

`config.txt` は microSDカードにイメージを書き込んだ直後、bootドライブ直下にあるので編集することができる。初期設定で入れるが吉。

## 使用方法

話が逸れたけど本題。シェルスクリプトについての使用方法を以下に記す。

### 1. シェルスクリプトDL

ターミナル上で以下実行。シェルスクリプトを DL。（コード量が多いので今回は git に置いた）

```shell
wget https://github.com/god1964/RaspberryPi/raw/main/init.sh
```

実行例は以下。

```shell-session
pi@raspberrypi4:~ $ wget https://github.com/god1964/RaspberryPi/raw/main/init.sh
--2021-12-12 18:12:32--  https://github.com/god1964/RaspberryPi/raw/main/init.sh
github.com (github.com) をDNSに問いあわせています... 52.69.186.44
github.com (github.com)|52.69.186.44|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 302 Found
場所: https://raw.githubusercontent.com/god1964/RaspberryPi/main/init.sh [続く]
--2021-12-12 18:12:32--  https://raw.githubusercontent.com/god1964/RaspberryPi/main/init.sh
raw.githubusercontent.com (raw.githubusercontent.com) をDNSに問いあわせています... 185.199.109.133, 185.199.111.133, 185.199.108.133, ...
raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 10039 (9.8K) [text/plain]
`init.sh' に保存中

init.sh                              100%[=====================================================================>]   9.80K  --.-KB/s 時間 0.001s

2021-12-12 18:12:32 (15.7 MB/s) - `init.sh' へ保存完了 [10039/10039]

pi@raspberrypi4:~ $
```

### 2. 自分の環境に合わせてパラメータ編集

カレントに `init.sh` が DL されたら、冒頭のパラメータを自分の環境に合わせて書き換える。

```shell
############################################################
# パラメータ定義
#
# interface          : 無線LAN="interface wlan0"、有線LAN="interface eth0"
# ip_address         : 固定IPアドレス
# routers            : ルーターアドレス。通例 192.168.*.1 か 192.168.*.254
# domain_name_servers: DNSサーバーの IPアドレス
# 
# ssid_buff          : SSID
# psk_buff           : パスワード
# 
# install_buff       : インストールパッケージ名
#
# confirm            : 確認メッセージ表示=true、確認メッセージ非表示=false
# diff_switch        : diffコマンド引数。未指定、-c、-u 等々
############################################################
# メニュー「2  : IPアドレス固定」が使用するパラメータ
interface="interface wlan0"
ip_address="192.168.11.***/24"
routers="192.168.11.1"
domain_name_servers="192.168.11.1"

# メニュー「8  : 複数AP対応 WiFi設定」が使用するパラメータ
ssid_buff[0]="***"
ssid_buff[1]="***"
ssid_buff[2]="***"

psk_buff[0]="***"
psk_buff[1]="***"
psk_buff[2]="***"

# メニュー「9  : 各種インストール」が使用するパラメータ
install_buff[0]="screen"
install_buff[1]="kodi"
install_buff[2]="wavemon"
install_buff[3]="iperf3"
install_buff[4]="gparted"

confirm=false
diff_switch=""
```

コメントにある通りだけど補足。
`domain_name_servers` は複数IP 指定する場合は以下のように半角スペース区切りで指定。

```shell
domain_name_servers="192.168.11.1 8.8.8.8"
```

IPアドレス固定を行わない場合は勿論設定不要。

`ssid_buff` は所謂アクセスポイント名、`psk_buff` はパスワード。
単純に配列をループで設定ファイルに書き込むので、5個でも10個でも定義すればそのまま書き込まれる。添え字のインデックスは必ず 0 始まりの連番であること。
また両配列の数は同じであること。
設定ファイルに書き込む際は優先順位（priority項目）を 1始まりで設定するので、優先順位が高い順で記述すること。

```shell
ssid_buff[0]="AAA"
ssid_buff[1]="BBB"
ssid_buff[2]="CCC"

psk_buff[0]="PWA"
psk_buff[1]="PWB"
psk_buff[2]="PWC"
```

と記述すれば、設定ファイルには以下のように書き込まれる。

```shell
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=JP

network={
        ssid="AAA"
        psk="PWA"
        priority=1
}

network={
        ssid="BBB"
        psk="PWB"
        priority=2
}

network={
        ssid="CCC"
        psk="PWC"
        priority=3
}
```

こちらもメニューから WiFi設定を行わないのであれば設定は不要。

というか、WiFi設定って最初に microSDカードのイメージを書き込んだ後に bootドライブに入れる `wpa_supplicant.conf` に複数AP定義を書き込んでおけばそこで完結するんだよな。
実はあまり意味がない機能だけど、不要ならスキップすればいいだけなので一応残した。

`install_buff` は各種インストールするパッケージ名を定義。必要な物は人それぞれなので書き換えて欲しい。
単純に `sudo apt install <install_buff[インデックス]> -y` と実行されるので、`install_buff[0]="network-manager network-manager-gnome"` と定義した場合は、
`sudo apt install network-manager network-manager-gnome -y` と展開し実行される。
つまり、一行に複数のパッケージ名を半角スペース区切りで設定するのもあり。お好みで。

`confirm` は、`true` に設定すると、メニューから選択した各処理実行時に確認メッセージが表示される。当初はそれがデフォルトの動作だったけど、動作確認を行っていたら煩わしかったので切り替え可能にした。

今回、設定ファイルを書き換える処理の後に、`diff`コマンドで変更箇所を表示するようにした。`diff_switch` はその際に指定するスイッチ。
context形式派の方は `-c` に指定するなりお好みで。何を言っているか分からない方はそのままでも無問題。

### 3. シェルスクリプト起動

以下コマンドでシェルスクリプト起動。`sh ～` での実行はエラーになるので注意。

```shell
bash ./init.sh
```

又は以下でも OK。お好みで。

```shell
chmod 744 init.sh
./init.sh
```

起動後のイメージは以下。

```shell
-----------------------------------------------
処理を選択してください
1  : パッケージ更新
2  : IPアドレス固定
3  : Swap領域無効化
4  : rsyslogログ出力抑制
5  : heartbeat設定
6  : Watchdog Timer設定
7  : Watchdog Timer設定後のハングアップテスト
8  : 複数AP対応 WiFi設定
9  : 各種インストール
q  : 終了
r  : 再起動
p  : シャットダウン
-----------------------------------------------
```

数字キー等押下、`Enter`キー押下で各処理を実行していく。
メニュー実行後、再起動が必要な場合は以下の確認メッセージが表示される。

```shell
再起動しますか？[y/n]
```

目視でエラーが発生していないことを確認し、`y`キー押下、`Enter`キー押下で再起動となる。
再起動後、再びスクリプトを実行し、続きのメニューを実行していく。
基本的に 1 から順番に実行していくことを想定しているが、不要なものを飛ばして構わない。
メニュー画面にある通り、`q`キーでシェルスクリプト終了。
一応、再起動、シャットダウンもメニューから実行できる。

またそれぞれのメニューは独立しているので任意のものだけ実行、でも構わない。
但し、メニュー 1 のパッケージ更新は、所謂パッケージインストール前に行うことが必須とされている `sudo apt update`等を行っているので、まずこれは実行した方が良い。
定期的にパッケージの更新は必要なので、今後も使えると思う。

メニュー 6、7 の Watchdog Timer設定だけはセットなので注意。メニュー 6 を実行しないでメニュー 7 だけを実行すると、単にフリーズするだけとなる。
メニュー 6 を実行していればフリーズ後、自動で再起動がかかる。
因みにこのフリーズ確認テスト。32bit版では数秒で再起動がかかったと記憶するが、64bit 版では数十秒かかるようなった。64bit版の方が負荷の耐性が高いということかな。

例によってエラーチェック機能は入っていない。目視で確認を想定。
ログ出力も無いので、必要ならターミナル側のログ出力機能を利用して欲しい。
設定ファイルは必ずバックアップを取っているので、万が一問題があっても何とかなるでしょう。

同じメニューを複数回実行しても実害はない筈。
インストール済みのものであれば、`apt`コマンド実行で、インストール済みです、と表示されるだけ。
設定ファイルも編集済みであるか判定しているので、編集済み（メニュー実行済み）の場合は空振りするだけ。

## 独自コードの追加

必要があれば独自コード、メニューを追加しカスタマイズして欲しい。
単純な構造なので改編は簡単だと思うが、一応コードの追加方法を示す。

### 1. メニュー追加

10番目のメニューを追加するものとする。echo でメニューを表示している箇所に一行挿入する。

```shell
  echo "9  : 各種インストール"
  echo "10 : カスタム処理"     <- 挿入行
  echo "q  : 終了"
```

### 2. 関数コール追加

case式でキーボードの入力対応した関数コール箇所に一行挿入する。

```shell
    "9")  job_9  "各種インストール" ;;
    "10") job_10 "カスタム処理" ;;      <- 挿入行
    "q")  job_exit ;;
```

### 3. 関数追加

以下のように関数を追加する。

```shell
#
# カスタム処理
#
function job_10() {
  job_confirm $1
  if "${cancel}" ; then
    return
  fi

  # コードを記述

  echo "$1が終了しました"
}
```

勿論 2 の呼び出し元と、3 の関数名が一致していれば関数名は何でも良い。

## 閑話休題

Raspberry Pi 400 日本版が発売された。要するに日本語配列キーボード版。公式でも発表されているけど……。
[Raspberry Pi 400 for Japan](https://www.raspberrypi.com/news/raspberry-pi-400-for-japan/ "Raspberry Pi 400 for Japan-Raspberry Pi")
日本語圏からと思われるレスポンスは皆無だというね。まさか「漢は黙って英語配列」って人ばかりでもないと思うんだけど。

## おまけ

今回のスクリプト化にあたり、まず最初に書いたテンプレートは以下。

<details><summary>クリックで表示</summary><div>

[template.sh](https://github.com/god1964/RaspberryPi/raw/main/template.sh "https://github.com/god1964/RaspberryPi/raw/main/template.sh")

```shell
#!/bin/bash

# 処理1
function job_1() {
  job_reboot
}

# 処理2
function job_2() {
  echo "$1を開始します。よろしいですか？[y/n]"
  read input
  if [ ! "${input,,}" = "y" ] ; then
    echo "処理2 を中断しました"
    return
  fi
  echo "$1が終了しました"
}

# 処理3
function job_3() {
  :
}

# 再起動処理
function job_reboot() {
  echo "再起動しますか？[y/n]"
  read input
  if [ "${input,,}" = "y" ] ; then
#    sudo reboot
     :
  else
    echo "再起動を中断しました"
  fi
}

# 終了処理
function job_exit() {
  echo "終了しますか？[y/n]"
  read input
  if [ "${input,,}" = "y" ] ; then
    exit
  else
    echo "終了を中断しました"
  fi
}

# メイン処理
while :
do
  echo ""
  echo "--------------------------------"
  echo "処理を選択してください"
  echo "1 : 処理1"
  echo "2 : 処理2"
  echo "3 : 処理3"
  echo "q : 終了"
  echo "--------------------------------"

  read input
  echo ""

  case "${input,,}" in
    "1") job_1 "処理1" ;;
    "2") job_2 "処理2" ;;
    "3") job_3 "処理3" ;;
    "q") job_exit ;;
    *)  echo "1～3、q を入力してください" ;;
  esac
done
```

</div></details>

単純な以下の構成であることが分かると思う。

### 1. 無限ループ
無限ループ開始。

### 2. メニュー表示、キー入力待ち
無限ループ内でメニュー表示、キー入力待ち。

### 3. 関数コール
キー入力に対応した処理を実行（関数コール）。
関数から戻ったら再び処理1 へ。

このコードに肉付けしたものが今回公開したシェルスクリプト。このテンプレート自体、流用できると思うので掲載した。
因みにこのテンプレートはダイクストラが提唱した [構造化プログラミング](https://ja.wikipedia.org/wiki/%E6%A7%8B%E9%80%A0%E5%8C%96%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0 "構造化プログラミング - Wikipedia") の要素である、`順次` `選択` `反復` を丁度満たしていて良いサンプル。新人研修にも使えるんじゃないかっていう。
シェルスクリプトなんて書いたこともないし、よく分からない、という方でも、実際に動かしてコードと見比べてもらえれば雰囲気が掴めるかと。
良い Raspberry Pi Life を。