Raspberry Pi OS (bookworm) インストール時に固定IPアドレスを設定する(続編)

#### Raspberry Pi OS (bookworm) インストール時に固定IPアドレスを設定する(続編)

前回の投稿で Raspberry Pi OS (bookworm) では WiFi 接続において固定IPアドレスが OSイントール時に設定できなくなったことを紹介しました。

https://qiita.com/pipito-yukio/items/8538bc50ba06f4aa2f7a

実を言うと全くできないかというとそうでもないのです。

**ただし今回紹介する方法は推奨される方法ではないので、もしこの方法を使ってみようと思っている方は自己責任でお願いします。**
※ まあ Raspberry Pi OS のインストールは SDカードベースなので失敗しても SD カードを焼き直すだけで済みますが:sweat:

**やり方としては SDカードに書き込まれたブートファイルシステム上のOSカスタマイズ用シェルスクリプトを改造するというものです。**

前々回の投稿では rpi-imager が生成した OSカスタマイズ用シェルスクリプトについて紹介いたしました。

https://qiita.com/pipito-yukio/items/6bbc5b6b47ec79120dad

OSカスタマイズ用シェルスクリプトの抜粋を下記に示します。
※全体のソースについては上記投稿記事をご覧ください。

```firstrun.sh
#!/bin/bash

set +e

CURRENT_HOSTNAME=`cat /etc/hostname | tr -d " \t\n\r"`
if [ -f /usr/lib/raspberrypi-sys-mods/imager_custom ]; then
   /usr/lib/raspberrypi-sys-mods/imager_custom set_hostname raspi-4-dev
else
   echo raspi-4-dev >/etc/hostname
   sed -i "s/127.0.1.1.*$CURRENT_HOSTNAME/127.0.1.1\traspi-4-dev/g" /etc/hosts
fi

# ...途中省略...

if [ -f /usr/lib/raspberrypi-sys-mods/imager_custom ]; then
   /usr/lib/raspberrypi-sys-mods/imager_custom set_keymap 'jp'
   /usr/lib/raspberrypi-sys-mods/imager_custom set_timezone 'Asia/Tokyo'
else
   rm -f /etc/localtime
   echo "Asia/Tokyo" >/etc/timezone
   dpkg-reconfigure -f noninteractive tzdata
cat >/etc/default/keyboard <<'KBEOF'
XKBMODEL="pc105"
XKBLAYOUT="jp"
XKBVARIANT=""
XKBOPTIONS=""

KBEOF
   dpkg-reconfigure -f noninteractive keyboard-configuration
fi
rm -f /boot/firstrun.sh
sed -i 's| systemd.run.*||g' /boot/cmdline.txt
exit 0
```

## 1 環境

### 1-1 検証用デバイス

+ Raspberry Pi zero 2w
    + インストールOS
        + Raspberry Pi OS with desktop 32bit bookworm
        2024-11-19-raspios-bookworm-armhf.img.xz

### 1-2 開発用PC

+ Ubuntu 22-04 Desktop
+ rpi-imager: [一般] Wi-Fiを設定する 
※カスタマイズした OSイメージをSDカードに書き込み済みであること

<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/d3c84d29-cacf-3124-0af6-99972b2406fd.jpeg">

+ ネットワーク接続
    各自お使いの環境のアドレスに置き換えて下さい
    + デフォルトルート: 192.168.0.1
    + 固定IPアドレス
        + Wi-Fi接続: 192.168.0.49/24

## 2 SDカードのマウント先確認
OS イメージが書き込まれた SDカードをマウントすると、私の環境では以下のようにマウントされました。

+ /media/yukio/bootfs
読み込み専用でマウントされる
※本来は改造することを前提としてない
+ /media/yukio/rootfs
読み込み書きモードでマウントされる
※ カスタマイズ用のファイルをコピー、又は編集が可能

```bash
$ lsblk -e7 | grep sdc
sdc                       8:32   1  28.9G  0 disk 
├─sdc1                    8:33   1   512M  0 part /media/yukio/bootfs
└─sdc2                    8:34   1   4.4G  0 part /media/yukio/rootfs
```

## 3 Wi-Fi接続設定ファイル

NetworkManager用 の Wi-Fi接続設定ファイルを生成し、NetworkManager が管理するディレクトリにコピーします。

### 3-1 Wi-Fi接続設定ファイル生成シェルスクリプト

#### 3-1-1 無線 LAN のパスフレーズ暗号化

下記のサイトを参考にさせていただきました。
[@IT itmedia: 無線LANのパスフレーズを暗号化するには？ wpa_passphraseコマンド](https://atmarkit.itmedia.co.jp/ait/articles/1601/23/news008.html)

ターミナルからコマンドを実行し出力結果を確認します。
※SSID と パスワードは適当な値に修正しています。

```bash
$ echo "abcdefghijkl" | wpa_passphrase xxxxxx2g-yyyyyy
# reading passphrase from stdin
network={
	ssid="xxxxxx2g-yyyyyy"
	#psk="abcdefghijkl"
	psk=1ebb370489744cc3ea554c2d61651b06361cb7b3b87bf9660d2b4dff4b7be7e1
}
```

(1) wpa_supplicant.conf  ファイルを生成するシェルスクリプト

psk=xxxxx 部分のみ必要なのですがこの値をコマンドから設定するのては大変なのでシェルスクリプトを作りました。
※暗号化したパスフレーズを抽出するためだけのものです。

```~/bin/raspi/gen_wpa_supplicant_conf.sh
#!/bin/bash

# ./gen_wpa_supplicant_conf.sh ssid encript_key

WPA_SUPP_CONF="${HOME}/bin/raspi/wpa_supplicant.conf"
SSID=
ENCRYPT_KEY=
if [ $# -eq 2 ]; then
   SSID=$1
   ENCRYPT_KEY=$2
else
   echo "SSID とパスワードが必要です。" >&2
   exit 1
fi  

# パスワードの暗号化実行
WPA_PASS=$(echo "${ENCRYPT_KEY}" | wpa_passphrase "${SSID}" | grep -vE "^\s*#")

cat <<- CONF_EOF >${WPA_SUPP_CONF}
country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
ap_scan=1

update_config=1
${WPA_PASS}
CONF_EOF
```

(2) シェルスクリプトの実行

+ 入力引数
※入力引数は全て架空の値にしています。
    + SSID: xxxxxx2g-yyyyyy
    + パスワード: abcdefghijkl

```bash
~$ cd ~/bin/raspi
~/bin/raspi$ ./gen_wpa_supplicant_conf.sh xxxxxx2g-yyyyyy abcdefghijkl
```

実際に出力したファイルの内容は以下のとおりです。
**※このファイル中の "psk" の値のみを次のシェルスクリプトで使います。**

```~/bin/raspi/wpa_supplicant.conf
country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
ap_scan=1

update_config=1
network={
	ssid="xxxxxx2g-yyyyyy"
	psk=1ebb370489744cc3ea554c2d61651b06361cb7b3b87bf9660d2b4dff4b7be7e1
}
```

#### 3-1-2 WiFi 接続設定ファイル生成するシェルスクリプト

NetworManager 対応の固定IPアドレスを設定する WiFi 接続設定ファイルを生成するシェルスクリプトを以下に示します。

このソースの元ネタは前々回紹介した投稿記事で紹介しましたが、SDカードをマウントしたときのルートファイルシステム上の下記ディレクトリにスクリプトが有ります。

```
/media/yukio/rootfs/usr/lib/raspberrypi-sys-mods/
|-- imager_custom        # set_wlan () 関数
```

以下のスクリプトは 前項 [3-1-1] のスクリプトで作成した wpa_supplicant.conf ファイルが存在することを前提に作成しています。

+ UUID の生成
※ Raspberry Pi OS (Debian) で UUID を生成するのは **```uuid```** コマンドですが、Ubuntu OS では **```uuidgen```** コマンドになります。
+ タイムスタンプの生成
date コマンドで UNIXタイムスタンプを生成します。


```~/bin/raspi/gen_staticIP_wifi_for_networkmanager.sh
#!/bin/bash

# ./gen_staticIP_wifi_for_networkmanager.sh id_name ip_address
# [example]
# ./gen_staticIP_wifi_for_networkmanager.sh myssid_staticIp 192.168.0.41

if [ $# -ne 2 ]; then
   SCRIPT_NAME=${0##*/}
   echo "Usage: ./${SCRIPT_NAME} id_name ip_address"
   exit 1
fi    
ID_NAME=$1
IP_ADDR=$2

# Required wpa_supplicant.conf
WPA_SUPP_CONF="$HOME/bin/raspi/wpa_supplicant.conf"
SSID=
PSK=
if [ -f $WPA_SUPP_CONF ]; then
   # SSIDはダブルクォートを削除する
   SSID=$(cat wpa_supplicant.conf | grep "ssid" \
 | sed -n -e 's/.*ssid=//gp' | tr -d '"')
   PSK=$(cat ${WPA_SUPP_CONF} | grep "psk" \
 | sed -n 's/.*psk=//gp')
else
   echo "${WPA_SUPP_CONF} not found!"
   exit 1
fi    

CONNFILE=~/bin/raspi/connections/${ID_NAME}.connection
UUID=$(uuidgen)
TIMESTAMP=$(date +'%s')

cat <<- EOF >${CONNFILE}
[connection]
id=${ID_NAME}
uuid=${UUID}
type=wifi
interface-name=wlan0
timestamp=${TIMESTAMP}

[wifi]
mode=infrastructure
ssid=${SSID}

[wifi-security]
key-mgmt=wpa-psk
psk=${PSK}

[ipv4]
address1=${IP_ADDR}/24,192.168.0.1
dns=192.168.0.1;
method=manual

[ipv6]
addr-gen-mode=stable-privacy
method=auto

[proxy]
EOF

chmod 600 ${CONNFILE}
LANG=C;ls -l --time-style long-iso ${CONNFILE} | grep -v total
cat ${CONNFILE}
```

シェルスクリプトの実行

+ 入力引数
    + 接続ID名: xxxxxx2g_staticIp
    + 固定IPアドレス: 192.168.0.49

```bash
~$ cd ~/bin/raspi
~$ ./gen_staticIP_wifi_for_networkmanager.sh xxxxxx2g_staticIp 192.168.0.49
```

実際に出力したファイルの内容は以下のとおりです。

```~/bin/raspi/connectins/xxxxxx2g_staticIp.connection
[connection]
id=xxxxxx2g_staticIp
uuid=031e4284-5e86-4e1b-a181-da523afe5d8a
type=wifi
interface-name=wlan0
timestamp=1735364864

[wifi]
mode=infrastructure
ssid=xxxxxx2g-yyyyyy

[wifi-security]
key-mgmt=wpa-psk
psk=1ebb370489744cc3ea554c2d61651b06361cb7b3b87bf9660d2b4dff4b7be7e1

[ipv4]
address1=192.168.0.49/24,192.168.0.1
dns=192.168.0.1;
method=manual

[ipv6]
addr-gen-mode=stable-privacy
method=auto

[proxy]
```

### 3-2 生成したWiFi 接続設定ファイルのコピー

生成した WiFi 接続設定ファイルを SDカードのルートファイルシステム上の下記ディレクトリにコピーします。

+ NetworkManager 管理ディレクトリ
**```/media/yukio/rootfs/etc/NetworkManager/system-connections```**

```bash
~/bin/raspi/connections$ sudo cp xxxxxx2g_staticIp.connection \  
> /media/yukio/rootfs/etc/NetworkManager/system-connections/
~/bin/raspi/connections$ ls -l --time-style long-iso \                        
> /media/yukio/rootfs/etc/NetworkManager/system-connections/
total 4
-rw------- 1 root root 429 2024-12-28 14:49 xxxxxx2g_staticIp.connection
```

## 4 OSカスタマイズ用スクリプト

OSカスタマイズ用スクリプトは読み込み専用のブートファイルシステム上に存在します。改造前に読み書きモードで再マウントします。

```bash
$ LANG=C; sudo mount -o remount,rw /media/yukio/bootfs
```

### 4-1 OSカスタマイズ用スクリプトの改造

改造する内容としては、 **OSカスタマイズ用スクリプトが生成したデフォルトのWi-Fi接続設定ファイル (preconfigured.nmconnection) を削除し、前項 [3-2] で生成した Wi-Fi設定ファイルを NetworkManager プロセスに読み込ませようとするものです。**

以下の行の上に改造スクリプトを追加します。( # ★ ここに処理を追加する)
```rm -f /boot/firstrun.sh```

改造する箇所のみを下記に抜粋して示します。
```/media/yukio/bootfs/firstrun.sh
#!/bin/bash

set +e

CURRENT_HOSTNAME=`cat /etc/hostname | tr -d " \t\n\r"`
# ...中略...
cat >/etc/default/keyboard <<'KBEOF'
XKBMODEL="pc105"
XKBLAYOUT="jp"
XKBVARIANT=""
XKBOPTIONS=""

KBEOF
   dpkg-reconfigure -f noninteractive keyboard-configuration
fi
# ★ ここに処理を追加する
rm -f /boot/firstrun.sh
sed -i 's| systemd.run.*||g' /boot/cmdline.txt
exit 0
```

#### 4-1-1 OSカスタマイズ用スクリプトの編集

ブートファイルシステム上のファイルを Vim エディタで直接編集します。

```bash
$ cd /media/yukio/bootfs
/media/yukio/bootfs$ ls -l --time-style long-iso firstrun.sh | grep -v total
-rw-r--r-- 1 yukio yukio 4020 2024-12-28 23:29 firstrun.sh
/media/yukio/bootfs$ sudo vi firstrun.sh 
```

追加した処理部分を下記に示します。
※ # ADD remove preconfigured.nmconnection file から ###...#### で囲った部分


```shell
KBEOF
   dpkg-reconfigure -f noninteractive keyboard-configuration
fi

# ADD remove preconfigured.nmconnection file ######################################
if [ -f /etc/NetworkManager/system-connections/preconfigured.nmconnection ]; then
   rm -f /etc/NetworkManager/system-connections/preconfigured.nmconnection
   echo "Remove preconfigured.connection"
fi
####################################################################################

rm -f /boot/firstrun.sh
sed -i 's| systemd.run.*||g' /boot/cmdline.txt
exit 0
```
以下は処理を追加中のスクリーンショットになります。

![Modifyed_raspios_bookworm_edit_firstrun_sh.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/7caf644c-8414-8f3d-0203-6602d1201d48.jpeg)

念の為編集内容を確認します。

```bash
/media/yukio/bootfs$ tail firstrun.sh 
# ADD remove preconfigured.nmconnection file ######################################
if [ -f /etc/NetworkManager/system-connections/preconfigured.nmconnection ]; then
   rm -f /etc/NetworkManager/system-connections/preconfigured.nmconnection
   echo "Remove preconfigured.connection"
fi
####################################################################################

rm -f /boot/firstrun.sh
sed -i 's| systemd.run.*||g' /boot/cmdline.txt
exit 0
```

### 4-2 ブートファイルシステムを元に戻す

ブートファイルシステムを読み込み専用モードにもどし、SDカードを取り外します。

```bash
/media/yukio/bootfs$ sudo mount -o remount,r /media/yukio/bootfs
/media/yukio/bootfs$ cd ~
$ sudo eject /dev/sdc
```

## 5 改造したイメージのインストール

SDカードを検証端末に差し電源をいれると、改造に問題なければインストール完了後のディスクトップ画面にWiFi接続のIPアドレスがトーストで表示されます。

生成した設定ファイルのIPアドレス (192.168.0.49) で Wi-Fi接続が確立されています。

![Raspi2w_b1_32bit_bookworm_networkConnected_3_modiifyed_wifiOnly.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/12b70c89-359a-ed62-7ed6-7e2406355946.jpeg)


### 5-1 ブートログの確認

ジャーナルのブートログを下記に示します。

(1) OSカスタマイズ用スクリプトの実行状況は最初のブートログ (-1) に出力されます。
(2) Wi-Fi接続の割当ての実行状況は最後のブートログ (0) に出力されます。
```bash
raspi@raspi-2w-dev:~ $ journalctl --list-boots
IDX BOOT ID                          FIRST ENTRY                 LAST ENTRY
 -1 7de97ba8b61a43edb51cd4110359ffb8 Tue 2024-11-19 22:44:59 JST Tue 2024-11-19 22:46:05 JST
  0 cc3c7ec09a9246888989cbea900c5680 Tue 2024-11-19 22:46:04 JST Sat 2024-12-28 11:00:30 JST
```

#### 5-1 (1) OSカスタマイズ用スクリプトの実行状況

最初のブートログをジャーナルから取得
**```$ journalctl -b -1 -o short-iso >journl-0-boot-1.log```**

タイムスタンプ列を省略し該当する部分のみ抜粋したものを示します。
※改造した処理によるメッセージが出力されています。
**```raspberrypi firstrun.sh[343]: Remove preconfigured.connection```**

```journl-0-boot-1.log
# ...中略...
raspberrypi kernel: Kernel command line: coherent_pool=1M 8250.nr_uarts=0 snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=1 snd_bcm2835.enable_hdmi=0  smsc95xx.macaddr=B8:27:EB:EF:F8:4C vc_mem.mem_base=0x1ec00000 vc_mem.mem_size=0x20000000  console=ttyS0,115200 console=tty1 root=PARTUUID=a427411d-02 rootfstype=ext4 fsck.repair=yes rootwait quiet splash plymouth.ignore-serial-consoles cfg80211.ieee80211_regdom=JP systemd.run=/boot/firmware/firstrun.sh systemd.run_success_action=reboot systemd.unit=kernel-command-line.target
# ...中略...
raspberrypi firstrun.sh[747]: Reloading keymap. This may take a short while
# ...中略...
raspberrypi firstrun.sh[343]: Remove preconfigured.connection
# ...中略...
```

#### 5-1 (2) Wi-Fi接続 (wlan0) の処理状況

最後のブートログをジャーナルから取得
**```$ journalctl -b 0 -o short-iso >journl-1-boot0.log```**

タイムスタンプ列とホスト列、その他冗長な情報を省略し該当する部分のみ抜粋したものを示します。
[タイムスタンプ+ホスト名] ```2024-11-19T22:46:16+0900 raspi-2w-dev```

(1) NetworkManager用 ID名 が読み込みされた箇所

```journl-1-boot0.log
# ...中略...
NetworkManager[444]: Wi-Fi P2P device controlled by interface wlan0 created
# ...中略...
NetworkManager[444]: policy: auto-activating connection 'xxxxxx2g_staticIp' (031e4284-5e86-4e1b-a181-da523afe5d8a)
NetworkManager[444]: device (wlan0): Activation: starting connection 'xxxxxx2g_staticIp' (031e4284-5e86-4e1b-a181-da523afe5d8a)
NetworkManager[444]: device (wlan0): state change: disconnected -> prepare (reason 'none', sys-iface-state: 'managed')
NetworkManager[444]: manager: NetworkManager state is now CONNECTING
# ...中略...
```

(2) Wi-Fi 接続で固定IPアドレス (192.168.0.49) が割り当てられた箇所

```journl-1-boot0.log
# ...中略...
NetworkManager[444]: policy: set 'xxxxxx2g_staticIp' (wlan0) as default for IPv4 routing and DNS
avahi-daemon[346]: Joining mDNS multicast group on interface wlan0.IPv4 with address 192.168.0.49.
avahi-daemon[346]: New relevant interface wlan0.IPv4 for mDNS.
avahi-daemon[346]: Registering new address record for 192.168.0.49 on wlan0.IPv4.
avahi-daemon[346]: Joining mDNS multicast group on interface wlan0.IPv6 with address fe80::d0f7:7544:d481:1447.
avahi-daemon[346]: New relevant interface wlan0.IPv6 for mDNS.
# ...中略...
NetworkManager[444]: device (wlan0): Activation: successful, device activated.
NetworkManager[444]: manager: NetworkManager state is now CONNECTED_GLOBAL
NetworkManager[444]: manager: startup complete
# ...以下省略...
```

### 最後に

**誰にでも薦められるものではない改造とはなりますが、ひとまず手っ取り早く Wi-Fi接続に固定IPアドレスを設定できる方法にはなると思います。**

私は Raspberry Pi OS (bookworm) と Raspberry Pi OS Legacy (bullseye) のネットワーク処理のソースコードを比較してみましたが、Raspberry Pi OS (bookworm) の処理は相当難しくなっています。

興味ある方は SDカードに OSを焼いたあと下記ディレクトリのソースをローカルのパソコンにコピーしてご覧になって見てください。ネットで色々探し回るよりソースを見たほうが早く解決することも有ります。

(1) ブートファイルシステム直下のファイル

**```/media/[yourname]/bootfs/```**
```
cmdline.txt
firstrun.sh
```

(2) firstrun.sh スクリプトから呼び出されるルートファイルシステム上のスクリプト

**```/media/[yourname]/rootfs/usr/lib```**

```bash
# ユーザーとパスワード設定を除く設定
raspberrypi-sys-mods/
|-- firstboot
|-- get_fw_loc
|-- i2cprobe
|-- imager_custom            # ★★ シェルスクリプト
|-- init_config
|-- regenerate_ssh_host_keys
`-- sshswitch
#
# ユーザーとパスワード設定
userconf-pi/
|-- userconf                 # ★★ シェルスクリプト
`-- userconf-service
```

(3) imager_custom スクリプトから呼び出される **raspi-config スクリプト**

**```/media/[yourname]/rootfs/usr/bin/raspi-config```**

それぞれの OS のソースを比較するとより理解が深まります。


### ソースコードについて

この記事で紹介したシェルスクリプトのソースコードは下記 GitHub で公開しています。
[(GitHab@pipito-yuko) / qiita-posts / RaspberryPi / RaspberryPiOS_bookworm](https://github.com/pipito-yukio/qiita-posts/tree/main/RaspberryPi/RaspberryPiOS_bookworm)

[ソースコード一覧]
```
src/bin/raspi/
├── connections
│   ├── Wired_staticIp.connection
│   └── xxxxxx2g_staticIp.connection
├── gen_staticIP_ethernet_for_networkmanager.sh
├── gen_staticIP_wifi_for_networkmanager.sh
├── gen_wpa_supplicant_conf.sh
└── wpa_supplicant.conf
```