ラズパイ最小インストールチャレンジ

更新：Debian標準カーネルが普通に起動できるようになったので、純正 Debian を作るときには https://qiita.com/kakinaguru_zo/items/e69adff9c92a959c9453 を見て下さい。おおよそ以下のことを自動的にやるスクリプトが https://github.com/emojifreak/debian-rpi-image-script/blob/main/raspberripyOS-rpi-sd-builder.sh にあります

なるべく少ないパッケージで起動・ログインできるラズパイSDカードを作るチャレンジです。`top`で表示される使用メモリ量は40 MB (32bit版)、`ps uww --ppid 2 -p 2 --deselect` で表示されるユーザープロセスは15個です。キーボードとディスプレイを繋がず、有線イーサネットを繋いでリモートログインできることを目指します。DebianまたはUbuntu Linux上でSDカードにddまたはcpで書き込めるイメージファイルを作る手順を示します。64-bit版作る手順を記述し、32bit版での違いはその都度注釈します。下記の動作確認はラズパイ 4Bで行っています。インストールするディストリビューションはラズパイカーネル・ラズパイブートローダーに、**Debian 11 Bullseye** または **Ubuntu 20.04 Focal** または **純正Raspbian OS** を組み合わせたものになります。以下の手順で作ったSDカードイメージを http://34.83.134.122/raspi/ からダウンロードできます。ダウンロードしたSDカードイメージに何か不具合があったらこの記事へのコメントで教えて下さい。rootのパスワードはrootです。なお、ウブンツブートローダーとウブンツカーネルを以下の手順で入れようとすると、付属する`linux-firmware-raspi2`でエラーが出て入りません。

## どの程度のメモリフットプリントを実現できるのか
`top` の出力の一部

```shell-session
top - 08:31:57 up 1 min,  1 user,  load average: 0.16, 0.07, 0.02
Tasks: 107 total,   1 running, 106 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  4.1 sy,  0.0 ni, 95.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   7875.9 total,   7771.9 free,     38.9 used,     65.2 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   7758.9 avail Mem 
```

`ssh`でログイン後に表示されるユーザープロセス一覧

```shell-session
root@raspi32bit:~# ps uww --ppid 2 -p 2 --deselect
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  2.4  0.1  34364  8368 ?        Ss   08:30   0:02 /sbin/init
root       111  0.7  0.0  31284  7176 ?        Ss   08:30   0:00 /lib/systemd/systemd-journald
root       130  0.4  0.0  17748  3784 ?        Ss   08:30   0:00 /lib/systemd/systemd-udevd
systemd+   137  0.4  0.0  24852  5496 ?        Ss   08:30   0:00 /lib/systemd/systemd-networkd
systemd+   190  0.5  0.0  24232  3120 ?        Ssl  08:30   0:00 /lib/systemd/systemd-timesyncd
message+   197  0.1  0.0   7464  3348 ?        Ss   08:30   0:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
root       200  0.3  0.0  14964  6308 ?        Ss   08:30   0:00 /lib/systemd/systemd-logind
root       202  0.0  0.0   4256  1416 tty1     Ss+  08:30   0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
root       208  0.0  0.0  11096  5428 ?        Ss   08:30   0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root       332  0.4  0.0  13336  6812 ?        Ss   08:31   0:00 sshd: root@pts/0
root       340  1.0  0.0  16336  7588 ?        Ss   08:31   0:00 /lib/systemd/systemd --user
root       344  0.0  0.0  35704  3200 ?        S    08:31   0:00 (sd-pam)
root       367  0.0  0.0   7888  3068 pts/0    Ss   08:31   0:00 -bash
root       373  0.0  0.0   9804  2636 pts/0    R+   08:32   0:00 ps uww --ppid 2 -p 2 --deselect
```

## ホストLinuxへの必要なツールのインストール

`apt-get install systemd-container qemu-user-static binfmt-support mmdebstrap gparted`

Raspberry Piアーカイブの署名鍵を

* https://archive.raspberrypi.org/debian/pool/main/r/raspberrypi-archive-keyring/
* http://archive.raspbian.org/raspbian/pool/main/r/raspbian-archive-keyring/

からダウンロードして、それを `dpkg -i` でインストールする。

また、UbuntuならびにDebianの最新の署名鍵を以下からダウンロードして `dpkg -i`でインストールする。

* https://packages.ubuntu.com/focal/ubuntu-keyring
* https://packages.debian.org/sid/debian-archive-keyring

## SDカードイメージファイルの作成とフォーマット

1. `dd seek=4096000 count=1 if=/dev/zero of=/var/tmp/rpisd.img` を実行すると大きさ約2 GBで、[ディスク容量をほとんど食わないスカスカ](https://qiita.com/SIGABRT/items/9541208804d315628ecd)のファイルができる
2. `losetup -P -f /var/tmp/rpisd.img` でこのファイルがデバイスっぽく見えるようになり、`fdisk` を使えるようになる
3. `fdisk /dev/loop0` でパーティションID 0x0cの大きさ256 MBの第一パーティションと、パーティションID 0x83で残り全部を占める第二パーティションを作り、 `w` コマンドでパーティションテーブルを書き込んで `fdisk` を終了する
4. `mkfs.vfat -F 32 /dev/loop0p1; mkfs.ext4 /dev/loop0p2` でそれぞれのパーティションをFAT32形式とext4形式でフォーマットする。ラズベリーパイカーネルはbtrfsを含んでいないので、btrfsを使いたいならinitramfsを駆使する必要があり、この記事では扱わない

## `mmdebstrap` を用いたパッケージのインストール
### `mmdebstrap` の下準備と実行

今更古臭くなったDebian Busterのパッケージを入れたくないので、Debian Bullseye (Testing) のパッケージを主に入れることにする。32bit版ならびにUbuntu Focalを入れる際の変更箇所はその都度述べる。64bit版の場合は、Raspbserry Pi Foundationが用意したパッケージとDebianのパッケージを混ぜてインストールする必要がある（これは普通のRaspberry Piでも同じ）

```config:/var/tmp/rpi-sources.txt
deb http://archive.raspberrypi.org/debian/ buster main
# 純正Raspbian OS 64bitの最小構成を作りたいなら
# deb http://deb.debian.org/debian buster main contrib non-free
# deb http://deb.debian.org/debian buster-updates main contrib non-free
# deb http://deb.debian.org/debian-security/ buster/updates main contrib non-free
# だけ書いて次行以降は無視する
# 純正Rasibian OSでも バックポート deb http://deb.debian.org/debian buster-backports main contrib non-free は使える
deb http://deb.debian.org/debian bullseye main contrib non-free
deb http://deb.debian.org/debian sid main contrib non-free
deb http://deb.debian.org/debian experimental main contrib non-free
```

**[32bitの場合Debian・Ubuntuと混ぜられる場合と混ぜられない場合があり](https://qiita.com/ysugi/items/04122e3b1792d872a182)、（混ぜられない場合では）以下を使う。**

```config:/var/tmp/rpi-sources.txt
deb http://raspbian.raspberrypi.org/raspbian/ buster main contrib non-free rpi
# もしBullseyeの新しいパッケージ要らなければ次行コメントアウト
deb http://raspbian.raspberrypi.org/raspbian/ bullseye main contrib non-free rpi
deb http://archive.raspberrypi.org/debian/ buster main
#まだ無い deb http://archive.raspberrypi.org/debian/ bullseye main
```

1. 上記のファイルを用意する
2. `mount -o async,discard,lazytime /dev/loop0p2 /mnt`
3. `mmdebstrap --architectures=arm64 (←32bit版はarmhf) --variant=apt  --include=あとで詳しく述べる bullseye /mnt - </var/tmp/rpi-sources.txt` とすると優先度が「Essential」のパッケージ、`apt`、`--include` で指定されたパッケージが`apt`を用いて `/mnt` 以下にインストールされる

#### Ubuntu インストールの際の変更点

```config:/var/tmp/rpi-sources.txt
deb http://archive.raspberrypi.org/debian/ buster main
deb http://ports.ubuntu.com/ubuntu-ports/  focal main restricted universe multiverse
deb http://ports.ubuntu.com/ubuntu-ports/  focal-updates main universe restricted multiverse
deb http://ports.ubuntu.com/ubuntu-ports/  focal-security main universe restricted multiverse
```
* `mmdebstrap` の標準入力に与える内容を上記のように変える
* `bullseye` を `focal` に変える
* インストールするパッケージに `language-pack-en` と `language-pack-ja` を追加する

**ここまでUbuntuのための変更点**

### `mmdebstrap` に指示するパッケージ
以下のパッケージをカンマで区切ってスペースなどを入れずに `--include=` に続けて `mmdebstrap` に与える。

#### Raspberry Pi 特有のパッケージ（Raspberry Pi Foundation製）
* raspberrypi-archive-keyring
* (Debian・UbuntuではなくてRasbianを用いる場合のみ) raspbian-archive-keyring
* raspberrypi-bootloader
* raspberrypi-kernel
* libraspberrypi-bin
* libraspberrypi0
* bluez-firmware
* firmware-atheros
* firmware-brcm80211
* firmware-libertas
* firmware-misc-nonfree
* firmware-realtek

#### 起動とログイン必要なパッケージ
* udev
* kmod
* e2fsprogs
* systemd-sysv
* libpam-systemd
* libnss-systemd
* dbus-user-session
* locales
* tzdata
* openssh-server
* bash

#### 無くても何とかなるけどこれくらいは欲しいパッケージ
* apt-utils
* whiptail
* vim-tiny (あるいはnano）
* less

#### Ubuntu を入れるときの追加パッケージ
* `language-pack-en`
* `language-pack-ja`
* これらを入れないと色々な奇妙な動作をUbuntuがする…

ここまでを `mmdebstrap` の `--include` に渡す。もっと多くのパッケージを入れたい場合は `--variant=` のところを `apt` ではなく `required` や `important` や `standard` にすると良い。

### SDカードの`/boot` の構成

1. `mount -o async,discard,lazytime /dev/loop0p1 /mnt2`
2. `cp -Rp /mnt/boot/* /mnt2`
3. `rm -rf /mnt/boot`
4. `mkdir /mnt/boot`
5. `umount /mnt2`
6. `mount -o async,discard,lazytime /dev/loop0p1 /mnt/boot`

**ここまで終わると `/mnt` 以下に Raspberry Pi起動後のファイルシステムが現れる**。以下 `/mnt` 以下にある設定ファイルを適切に編集していく。

## 設定
### `/mnt/boot` 以下の設定
1. 既に可動実績があるSDカードの中の `/boot/config.txt` と `/boot/cmdline.txt` を `/mnt/boot` 以下にコピーする
1. `blkid /dev/loop0p2` で今作っているルートファイルシステムの `PARTUUID` を調べる
2. それを `/mnt/boot/cmdline.txt` に書く
3. `cmdline.txt` の中に`init=` がもしあれば`init=/sbin/init` とでも変えておく

### `/mnt/etc` 以下の設定
* `/dev/loop0p1` と `/dev/loop0p2` のPARTUUIDを例えば以下のように `/mnt/etc/fstab`に反映させる

```config:/etc/fstab
PARTUUID=1a25766f-01 /boot vfat defaults,lazytime,async,discard 0 2
PARTUUID=1a25766f-02 /     ext4 defaults,lazytime,async,discard 0 1
```
* `/mnt/etc/hostname` を適切に設定する
* `/mnt/etc/resolv.conf` を適切に設定して (`chattr +i` して)おく。例えば

```config:/etc/resolv.conf
options inet6 edns0
nameserver 8.8.4.4
nameserver 8.8.8.8
nameserver 1.0.0.1
```
* `/mnt/etc/apt/apt.conf.d/00myconf` に `APT::Default-Release "bullseye";` を書いておいて `unstable` などのパッケージが入らないようにする。ここはDebian BusterやUbuntu Focalを入れようとしているなら不要。
* 異常が起きたときにディスプレイとキーボードを繋いでいないため `Storage=persistent` を `/mnt/etc/systemd/journald.conf` に書いておいて、ログがSDカード内の `/var/log/journal` に残るようにする。好みに合わせて同じディレクトリの `logind.conf`, `user.conf`, `systemd.conf` も変更する
* `/mnt/etc/ssh/sshd_config` の`PermitRootLogin` を`yes`にする。SSH公開鍵を使っているならそれを `/mnt/root/.ssh/authorized_keys` に含める
* IPアドレスなどネットワークの設定を行うパッケージは `systemd` 以外何も入っていないので、例えば以下のようなファイルを作りネットワークの設定を`systemd` に教え込む

```config:/mnt/etc/systemd/network/ether.network
[Match]
Name=e*
[Network]
DHCP=yes
NTP=ntp.nict.jp
```

### Raspberry Pi エミュレータの中での設定
1. `systemd-nspawn -D /mnt -a` でARMエミュレーターに入る
2. `passwd` で`root`パスワードを設定
3. `dpkg-reconfigure tzdata` でタイムゾーンを設定
4. `dpkg-reconfigure locales` でロケールを設定
5. `systemctl enable systemd-networkd` で`systemd`によるネットワーク設定が行われるようにする
6. `systemctl enable ssh` でsshサーバーを自動起動させる
7. 追加のパッケージインストールや設定をここで行える（後述）
7. `exit 0`

### 後始末
1. `umount /mnt/boot`
2. `umount /mnt`
3. `losetup -D`

## 作成したSDカードイメージ

ここまでの手順で作成したイメージファイルは http://34.83.134.122/raspi/ からダウンロードできます。rootのパスワードはrootです。

## SDカードへの書き込みとルートパーティションの拡大

1. `cp /var/tmp/rpisd.img /dev/mmcblk0; sync`
2. `gparted /dev/mmcblk0` で第二パーティションのext4ファイルシステムをSDカード一杯にリサイズする
3. `blkid /dev/mmcblk0p*` でPARTUUIDが変わっていないか確認する。変わっていたら SD カード内の `/boot/cmdline.txt` と `/etc/fstab` を必要に応じて更新する

## 追加パッケージ
好みや用途に合わせて`systemd-nspawn`して起動するエミュレーターの中でパッケージや設定の追加もできます。余計なパッケージが入らないようにしたい場合 `/etc/apt/apt.conf.d/00myconf` に `APT::Install-Recommends 0;` と書いておくか `apt-get`に`--no-install-recommends` を付けてインストールすると良いです。

### 無線の設定
無線LANはラズパイのLinuxから`wlan0`と見えているとします。名前は `ip l` コマンドで確認できます。

```config:/etc/wpa_supplicant/wpa_supplicant-wlan0.conf
country=JP
network={
  ssid="あなたのSSID"
  scan_ssid=1
  key_mgmt=WPA-PSK
  psk="パスワードを平文で"
}
```

1. 上記のように設定ファイルを作る
2. `apt-get install crda wpasupplicant`
3. `systemctl disable --now wpa_supplicant`
4. `systemctl enable wpa_supplicant@wlan0`
5. `/etc/systemd/network/wireless.network` を前述の`ether.network` と同様に適切に作成する
6. `systemctl reboot`



## 挑戦者をお待ちしております

出来合いのSDカードイメージをSDカードに書き込むだけではなくて、自分の用途に合わせてどんどん自作しましょう。Qiita記事やブログ書いたらコメント欄などで教えて下さい

## 先人の記事
* [ちいさな Raspbian Stretch イメージの作成](https://qiita.com/mt08/items/9e336d3f3ccba48c5121)
* [Raspbianをdebootstrapから作る](https://qiita.com/abtc/items/862e2999d7c136201a44)
* [Raspberry Pi 3/3+でarm64なOSイメージ生成](https://qiita.com/realteck/items/352bc5dca6c541b8de44)
* [Raspberry Pi 3 で 公式 arm64 Debian を動かす](https://qiita.com/ysugi/items/04122e3b1792d872a182)
* [RaspberryPi3用 の 64bit OSイメージ(Debian) の作成手順](https://qiita.com/PINTO/items/d685d6dba66b2477ab2e)
* [Raspberry Pi3用Debianイメージを色々なアーキテクチャに対応させて作成する](https://qiita.com/maccadoo/items/2986afa81ba96321c818)

## GUIの追加
GUI使うなら純正Raspberry Pi OS使っとけばいいと思いますけど、一応書いておきます。

### Ubuntu の場合
[Ubuntu MATE for Raspberry Pi](https://ubuntu-mate.org/ports/raspberry-pi/) を使いましょう。かなりよく出来ています。4Kディスプレイにつないで割と快適にユーチューブ見られます。

### Debian の場合

1. network manager を使うから、`/etc/systemd/network/` 以下にある `systemd-networkd` の設定はすべて消すか `[Link] Unmanaged=yes` を入れておく
2. `apt-get --no-install-recommends install wpasupplicant crda network-manager ca-certificates libglib2.0-data shared-mime-info xdg-user-dirs publicsuffix man-db manpages manpages-ja console-setup keyboard-configuration`
3. `apt-get --install-recommends install appmenu-gtk3-module libcanberra-gtk3-module task-gnome-desktop task-japanese-gnome-desktop task-japanese-desktop` Gnome 以外のデスクトップを使うときは、`task-gnome-desktop` の部分を `task-mate-desktop` などに置き換えて下さい。

* 前述のUbuntu Mate よりもDebian Gnome (Wayland)は遥かに画面描画などが遅いです。ユーチューブ視聴が困難なレベルです。
* Debian Mateはそこそこ使えますけど、前述のUbuntu Mateのほうが描画をよく最適化してきて快適です。
* Debian Bullseye と勝手に組み合わせると `xserver-xorg-video-fbturbo` はエラーが出てX自体が立ち上がらなくなるため、インストールしないで下さい。