Raspberry Pi	OS Liteをヘッドレスでインストールする(bookworm対応)

　EWI-USB用の音源をRaspberry Pi 3A+で作ろうと思って、Raspberry Pi OS Liteをできるだけ軽く動くようにヘッドレスで設定をした記録です。以前はbullseyeでやってたんですが、使いたいシンセサイザーエンジンがbookwormを必要とし始めたので、やり直しました。bullseyeまでとずいぶん勝手が違いますね。

# OSイメージの書き込み
　2022年春以降、Raspberry Pi OSのイメージファイルからデフォルトユーザが削除されました。その分、Raspberry Pi Imager（https://www.raspberrypi.com/software/ から入手、以下RPiImager）の機能が拡充され、ユーザの設定だけでなくSSHやWi-Fiおよび使用国、ロケールを詳細設定(右下の歯車マーク)からあらかじめ指定してmicroSDに書き込めるようになっているので、これを利用するのが一番良い選択だと思います。

:::note warn
　RPiImagerのバージョンには注意してください。Windows環境ではダウンロードしたインストーラを素直に起動すれば最新版がインストールされますが、筆者の母艦(Ubuntu 22.04.4 LTS)では、公式ページからダウンロードしたdebファイルを開いても、ページで案内されている`sudo apt install rpi-imager`を実行しても、インストールされたのはリポジトリに登録されていた1.7.2に留まっていました。
　1.7.2で提供される`firstrun.sh`ではWi-Fiの設定がdhcpd前提のままで、bookwormで標準化されているNetworkManagerの設定ができません。「bookwormではヘッドレス設定はできないらしい」と解説しているページもあったのは、このせいかも。
　あれこれ調べて結局、最新版(執筆時点でバージョン1.8.5)をインストールする方法は
~~~
sudo snap install rpi-imager
~~~
でした。困っている方はお試しを。
:::

　RPiImagerではOS が選べますが、今回は「Raspberry Pi OS(other)」を選んで出てくる64ビットLiteをRPi3A+で利用しました。Zeroや2以前は32ビットOSしか使えませんが、手順としては同様です。
![Screenshot from 2024-08-18 15-48-33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545256/443d5105-ab41-6c8c-dd94-f29b3cd5460d.png)
　ユーザー設定、Wi-fiの設定、使用国と言語、SSHの有効化を設定して、書き込みへ進みましょう。

# 起動前に設定できることはやっておきたい
### Wi-FiのIPアドレスを固定したい
　DHCPでIPアドレスをもらってくる時間を端折りたかったので、固定しました。
　RPiImagerは詳細設定を`bootfs`にある`firstrun.sh`に書き込み、初回起動時に実行することで各種設定を進めていくので、事前にこのファイルへ埋め込んでしまいます。
　下から3行目に
~~~command:firstrun.sh
rm -f /boot/firstrun.sh
~~~
とあるので、この前に追加しました。たぶん最終行`exit 0`の前ならいいんだと思いますが、実行される前にスクリプトが削除されるのが気持ち悪いのでここにしています。

　アドレス固定、DNSはGoogleパブリック、使う気がないIPv6は無効化、の設定を追記します。

~~~
systemctl start NetworkManager.service
nmcli connection reload
nmcli connection modify preconfigured ipv4.method "manual" ipv4.addresses "192.168.xx.yy/24" ipv4.gateway "192.168.xx.zz"
nmcli connection modify preconfigured ipv4.dns 8.8.8.8
nmcli connection modify preconfigured ipv6.method disabled
nmcli connection down preconfigured
nmcli connection up preconfigured
~~~

　`preconfigured`は初回起動時の処理の中で作られるWi-Fiコネクションの識別子です。NetworkManagerがどのタイミングで起動しているか判別できなかったので、サービスを起動→コネクションの読み直し→アドレス固定、DNS設定、IPv6無効化→コネクション落として上げなおし、という手順にしています。
　念のため記しておくと、`192.168.xx.yy`が固定したいアドレス、`192.168.xx.zz`がゲートウェイのアドレスです。お手許の環境に合わせて書き換えてください。`8.8.8.8`はGoogleパブリックDNSのアドレスです。

:::note warn
DNSをGoogleパブリックにしていると、せっかくRPiImagerでホスト名を付けてもDNSに登録されないので、後の作業でホスト名でのアクセスができません。ルータがDNSを提供しているなら、DNSのアドレスはルータのアドレスにします。
:::

:::note info
https://netlog.jpn.org/r271-635/2024/04/raspberrypi_install_ver_bookworm.html
　こちらのページでは`firstrun.sh`が何をやっているかを詳細に見ておられていて大変興味深いのですが、このバージョンでは`firstrun.sh`が途中で呼び出す`imager_custom`で一発モノのデーモンを作って、再起動後にそれが動いて`preconfigured`を作っています。先述のRPiImager1.7.2ではこのような動作がなく`wpa_supplicant.conf`を作るのみでした。今回使った1.8.5では動作がまた変更されていて、`imager_custom`は`preconfigured.nmconnection`を直接作っています。そのため本稿ではこのようにしていますが、RPiImagerのバージョンが変わったらまたやり方を変えなければいけないかもしれません。
:::

## config.txtの設定
　`bootfs`にある`config.txt`は、初回起動時に`/boot/firmware/cmdline.txt`にコピーされます。ここで編集しておくと便利です。
　`[all]`の下に追加していきます。以下は筆者の好みです。
~~~conf:config.txt
# メモリの割り当て変更：デフォルトではGPUに64MB振られているのを最低の16MBに
gpu_mem=16
# 動作クロックをターボモードに 
force_turbo=1
# Bluetooth無効
dtoverlay=disable-bt
# 電源LEDはデフォルトではパワーLEDは稼働時もシャットダウン後も点灯したままなので
# 点滅させて稼働中なのがわかるように
dtparam=pwr_led_trigger=heartbeat
~~~
:::note info
　`bootfs`にある`cmdline.txt`も、`config.txt`同様に初回起動時に`/boot/firmware/cmdline.txt`にコピーされます。
　以前のOSでは「`cmdline.txt`に`quiet`を追加して起動時のメッセージを抑制、起動時間を稼ぐ」というのをやっていましたが、今回のOSイメージでは`cmdline.txt`にもともと`quiet`って書かれてたので編集していません。他に指定するような設定があるなら、ここで先に設定しておくのは有用だと思います。
:::

# 起動後に行う設定
　`bootfs`に配置されている設定ファイルは先にいじることができますが、`rootfs`にあるファイルは権限が設定されていたりシステムで書き換えられたりするので、起動後に設定することになります。
　準備したmicroSDをRPiにセット、起動したら、RPiImagerの詳細設定でつけたホスト名もしくは固定したIPアドレスにSSHでアクセスし、最初の設定を進めていきます。

:::note info
　IPアドレスをDHCPから得ている場合、DHCPの払い出しログなどでRPiのMACアドレス`B8:27:EB:〜`、`DC:A6:32:〜`、`E4:5F:01:〜`のいずれかに対応するIPアドレスを探します。
:::

## パッケージの更新
　この後でテンポラリフォルダのRAMディスク化をしてしまうとうまくいかなくなることがあるので、先にやってしまいましょう。
　`apt`は`upgrade`でいいか`dist-upgrade`までやるかは状況によると思います。
~~~shell:command
sudo apt update
sudo apt -y upgrade
sudo apt -y dist-upgrade
sudo rpi-update
~~~

## 起動スピードを稼ぐ
　ちょっとだけでもと思ってやっています。

### 必要なさそうなサービスの停止
　`firstrun.sh`に書き込んで先にやってしまうのを試してみたんですが、うまくいかなかったので起動後に。
~~~shell:command
sudo systemctl disable avahi-daemon
sudo systemctl disable systemd-timesyncd
sudo systemctl disable bluetooth
sudo systemctl disable hciuart
sudo systemctl disable ModemManager
sudo systemctl disable rsync
sudo systemctl disable triggerhappy
~~~
:::note info
検索したり雑誌を読んだりしていると、`ofono`、`psplash-start`、`psplash-systemd`、`plymouth`も止めましょうという話もありますが、今回のインストールではもともと入っていませんでした。
:::

### aptの日次更新を止める
　`AutoAptEnable`を`0`に。エディタで開いて該当部分を書き換えればいいのですが、たった1箇所を探すのが面倒なので`sed`でやっちゃいます。
~~~shell:command
sudo sed -i 's/^AutoAptEnable=1/AutoAptEnable=0/' /usr/lib/apt/apt.systemd.daily
~~~

### ntp問い合わせ先の設定
　上記で`systemd-timesyncd`を止めてしまっているので起動スピードには直接関係がないですが、起動後に時刻合わせをしたくなった時、ntpが聞きに行くサーバが遠いとレスポンスも遅くなるので、日本用のを使うようにします。
　こちらを参考に、timesyncdの設定を変更しました。

https://www.mk-mode.com/blog/2023/08/16/debian-12-systemd-timesyncd/

~~~shell:command
sudo nano /etc/systemd/timesyncd.conf
~~~

~~~/etc/systemd/timesyncd.conf
[Time]
NTP=ntp.nict.jp
FallbackNTP=ntp1.jst.mfeed.ad.jp ntp2.jst.mfeed.ad.jp ntp3.jst.mfeed.ad.jp
~~~

:::note info
時刻が合っていないと、SSL通信をする際の証明書照合でエラーになります。aptやpipなどでパッケージのインストールや更新をするときにSSL通信が必要なことがあるので、その前には時刻合わせをしておく必要があるでしょう。
~~~shell:command
cat >~/timeadjust.sh <<EOF
#!/bin/bash
sudo systemctl enable systemd-timesyncd.service
sudo systemctl restart systemd-timesyncd.service
sudo systemctl disable systemd-timesyncd.service
EOF
chmod +x ~/timeadjust.sh
~~~
として、ホームディレクトリに`timeadjust.sh`を作っておけば
~~~shell:command
~/timeadjust.sh
~~~
で時刻合わせができます。
:::

## SDカードの延命処置
　参考にしたのはこちらです。

https://silver0480.blog.fc2.com/blog-entry-464.html

　当時はbullseyeだったのでありがたくこの通りやらせていただいていましたが、bookwormで少し事情が変わっています。
　なお、IoTシステムなんかで長期間手許を離れるようなものだとF2FSのようなフラッシュメモリ向けファイルシステムへの変更までしておいたほうがいいのでしょうが、手間がかかるので今回はやっていません。

### ログの出力を停止
　ログはbookwormではjournaldに集約されています。

https://qiita.com/pyon_kiti_jp/items/4ea17612b3d86cce1e5b

　ログの保存場所はデフォルトで自動的に決定され、あれば`/var/log/journal`、なければ`/run/log/journal`です。`df -T`で確認すると、`/run`はファイルシステムがtmpfsですが、これは後述の設定でRAMディスク化してしまうので、`/var/log/journal`が存在しなければSDカードには書き込まれないことになります。
~~~shell:command
ls -r /var/log/journal
~~~
で確認しておきます。
~~~
ls: cannot access '/var/log/journal': No such file or directory
~~~
と出なかった(つまり存在している)場合は、
~~~shell:command
sudo rm -r /var/log/journal
~~~
で削除しておきましょう。

### ログの更新頻度とバックアップを抑制
　`/var/log/`には他のログがあり、それがローテーションでバックアップされていくのがSDカードを傷めていくと言われているので、止めてしまいます。
　`logrotate`の設定ファイルを`sed`で書き換えます。頻度を`monthly`に、ローテーションを`rotate 0`にします。
~~~shell:command
sudo sed -i 's/daily$/monthly/' /etc/logrotate.conf
sudo sed -i 's/weekly$/monthly/' /etc/logrotate.conf
sudo sed -i 's/rotate .*$/rotate 0/' /etc/logrotate.conf
~~~
　また、`/etc/logrotate.d/`には個別のログ更新設定ファイルが保存されているので、全部書き換えます。
　この中には`rotate`の指定が2か所あるものもあるので、`g`をつけて全て対象にします。
~~~shell:command
sudo sed -i 's/daily$/monthly/g' /etc/logrotate.d/*
sudo sed -i 's/weekly$/monthly/g' /etc/logrotate.d/*
sudo sed -i 's/rotate .*$/rotate 0/g' /etc/logrotate.d/*
~~~

### スワップファイルのRAMディスク化
　まず、スワップファイルを止め、dphysを削除します。 
~~~bash:command
sudo swapoff --all
sudo /etc/init.d/dphys-swapfile stop
sudo apt purge -y dphys-swapfile
~~~
　Zramの導入は一発でできるようになってます。ありがたいことです。
　aptのアップデートはやっておきましょう。
~~~shell:command
sudo apt update
sudo apt install -y zram-tools
~~~
　今回の環境では調整するほどのRAMサイズが元々ないので、デフォルトのままにしました。RAM容量の大きいモデルを利用する場合は調整を考えてください。

### tmpフォルダのRAMディスク化
　大変参考になった元ネタはこちら。

https://qiita.com/girlfellfromsky/items/953ad71ad6ed09a97dfe

~~~bash:command
sudo nano /etc/fstab
~~~
次の3行を追記します。
~~~conf:/etc/fstab
tmpfs /tmp     tmpfs defaults,size=32m,noatime,mode=1777 0 0
tmpfs /var/tmp tmpfs defaults,size=16m,noatime,mode=1777 0 0
tmpfs /var/log tmpfs defaults,size=16m,noatime,mode=0755 0 0
~~~
　[`/var/tmp`はRAMディスク化するな、というのがArch wikiに書かれている](https://wiki.archlinux.jp/index.php/Tmpfs)そうです。筆者の環境では問題なく動いていますが、やめた方が無難かもしれません。

::: note warn
これを行ってしまうと、後々導入するライブラリやパッケージが領域不足でビルドできなくなることがありました。RPi3A+を使いましたが、RAMディスクの容量を64MB以上にするとビルド中にハングアップしてしまいます(たぶんメインメモリが足りない)。ビルド中に画面が止まるようなら失敗とみて、このビルドの間だけRAMディスク化を解除したほうが良さそうです。
~~~shell:command
sudo sed -i 's/^tmpfs /#tmpfs /g' /etc/fstab
~~~
行の先頭に`tmpfs`があれば#をつけてコメントアウト、というコマンド。実行したら再起動して、ビルドし直してください。終わったら逆の
~~~shell:command
sudo sed -i 's/^#tmpfs /tmpfs /g' /etc/fstab
~~~
を行って再起動すればOKです。
:::

## クリーンアップ
　[先述の元ネタ](https://qiita.com/girlfellfromsky/items/953ad71ad6ed09a97dfe)ではRAM環境で起動を確認してからにされてますが、横着してリブートせずに進めます。真面目な方は元ネタの通りに進めたほうがいいでしょう。
~~~shell:command
sudo apt autoremove
sudo apt autoclean
sudo rm -rf /var/lib/apt/lists/*
sudo rm -rf /var/cache/apt/archives/*
sudo rm -rf /tmp/*
sudo rm -rf /var/tmp/*
sudo rm -rf /var/log/*
sudo rm /var/swap
~~~
　再起動後にもろもろ有効になります。

# その他、注意点
* bookwormではオーディオサブシステムがPulseAudioからPipeWireへ変更された、という話ですが、RPiOS LiteではPipeWireどころかPulseAudioすら入っていませんでした。
* pythonで利用していたRPi.GPIOが廃止されています。gpiozeroなどに移行する必要があります。
* pythonの環境は仮想化が推奨されるようになりました。とはいえ、RPiの環境なんて仮想化せずとも、プロジェクトごとに安いSDカードを差し替えれば良い話と思っているので、ライブラリの導入時にはpipの`--break-system-packages`オプションで強引にやってしまっています。`sudo`でやるとインストール後「venvとか使えや」って言われますが、筆者は知らん顔しています。ちゃんとvenvで対応したい方はこちらの素晴らしい記事を参考にしてください。

https://blog.jp.square-enix.com/iteng-blog/posts/00043-play-with-the-pep668/

　なにか気づけば追記していきます。

# 余談
　このあたりのコマンドは動作確認に役に立ちました。
~~~shell:command
# インストールされているパッケージをすべて表示
sudo apt list --installed
# 現在実行されているサービスの一覧
sudo systemctl | grep running
# システム起動時のパフォーマンス
systemd-analyze time
# systemd各ユニットの初期化時間
systemd-analyze blame
# CPUの温度
vcgencmd measure_temp
# CPUの現在のクロック数
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
~~~