俺用Raspberry Pi Zeroセットアップ手順書

docker+ansibleで自動設定できるようにしました。https://github.com/ruly-rudel/env

## やりたいこと

- USB OTGでハブ経由のUSBのイーサネットとwifiデバイス接続、NAPT
- USB Audio Classデバイス(Babyface)を接続し、shairport-syncでAirPlayサーバー
  - 可能な限りbit exactと思われる設定で

## ポイント

- BabyfaceはS32_LEフォーマットのみ出力対応のため、ALSAのhw:Babyface...に出力するときはshairport-sync側で出力フォーマットを設定する必要がある
  - plughw:Babyface...を利用すればpcmプラグインがよしなに変換してくれるのでこの設定は必要ない
  - が、plughw:Babyface...はサンプリングレートも変換できてしまう。サンプリングレートはソフトでは変換したくなかった&ALSAの仕様をイマイチ理解していないので、念のため変換を一切挟まないと思われるhw:Babyface...を利用した
- SDカードへの書き込み回数を減らすため、スワップは停止し/tmp, /var/tmp, /var/logをtmpfsに
- link localアドレス使ってなんとかDHCPつかわんでもavahiだけでいけるかなーと考えたけど、defaultrouterとdns serverをavahiだけで配布するのが大変そうなのであきらめてdnsmasq入れた
    - DNSサーバー、nftablesで名前解決リクエストをDNATするだけで動くんだけど、DNSサーバーがNAT先の回線が切れたり繋がったするたびに変わる可能性があるので/etc/resolv.confみてくれるdnsmasqのDNSサーバーを使うことにした
        - nft add chain ip nat PREROUTING iifname eth0 udp dport 53 dnat (DNSサーバーのipアドレス)
        - eth0に来たUDP53番ポートに来たリクエストを全部DNSサーバーにDNATする設定
- dnsmasq.confに元から書いてあるおしり二行の謎設定はどうもDHCP配る先がraspberry piの場合のworkaroudみたい。なのでらずぱいに配布しなければ削除しても多分大丈夫
    - --dhcp-reply-delay=[tag:<tag>,]<integer>
Delays sending DHCPOFFER and PROXYDHCP replies for at least the specified number of seconds. This can be used as workaround for bugs in PXE boot firmware that does not function properly when receiving an instant reply. This option takes into account the time already spent waiting (e.g. performing ping check) if any.

## 手順

### SD Card作成から起動まで
- Raspberry Pi OS LiteをMicro SDXCに焼いて¥sshをtouch、¥wpa_supplicant.confを下記のように設定
- SDカードをラズパイに挿入してネットワーク上にDHCP serverがある状態で起動
- ssh pi@raspberrypi.localし、初期パスワード「raspberry」でログイン

```configuration:wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=JP

network={
        SSID="(SSIDを記載)"
        psk="(keyを記載)"
}

network={
...
}
```

### OS upgradeとパッケージ導入
```shell-session
% sudo apt-get update
% sudo apt-get full-upgrade

% sudo apt-get install shairport-sync
% sudo apt-get install nftables
% sudo apt-get install dnsmasq

% sudo raspi-config
(ホスト名変更、パスワード変更)
```

### swap停止

```shell-session:swap停止
% sudo swapoff --all
% sudo systemctl stop dphys-swapfile
% sudo systemctl disable dphys-swapfile
```

### tmpfs configurations

```configuration:/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=32m,noatime,mode=0755	0	0
```

```configuration:/etc/init.d/tmpfs-logs.sh
#!/bin/sh

LOGFILES="/var/log/lastlog /var/log/wtmp"
touch $LOGFILES
chmod 664 $LOGFILES
chown root:utmp $LOGFILES
mkdir -p /var/log/fsck
exit 0
```

```configuration:/etc/systemd/system/tmpfs-logs.service
[Unit]
Description=tmpfsLogs
RequiresMountsFor=/var/log

[Service]
Type=simple
User=root
WorkingDirectory=/var
ExecStart=/etc/init.d/tmpfs-logs.sh

[Install]
WantedBy=multi-user.target
```

```shell-session:tmpfsをmount
% sudo rm -rf /tmp
% sudo mkdir -m 1777 /tmp
% sudo chown root:root /tmp
% sudo mount /tmp

% sudo rm -rf /var/tmp
% sudo mkdir -m 1777 /var/tmp
% sudo chown root:root /var/tmp
% sudo mount /var/tmp

% sudo rm -rf /var/log
% sudo mkdir -m 755 /var/log
% sudo chown root:root /var/log
% sudo mount /var/log

% sudo systemctl --system daemon-reload
% sudo systemctl enable tmpfs-logs.service
% sudo systemctl start tmpfs-logs.service
```

### Network configuration (dhcpcd5)

```configuration:/etc/dhcpcd.conf
...
interface eth0
static ip_address=xxx.xxx.xxx.xxx
...
```

### wifi workaround

```configuration:/etc/systemd/network/50-wlan.link
[Match]
Type=wlan

[Link]
Name=wlan0
```

### NAPT using nftables

```configuration:/etc/nftables.conf
(ファイル終端に以下を追記)

table ip nat {
        chain PREROUTING {
                type nat hook prerouting priority 0;
        }
        chain POSTROUTING {
                type nat hook postrouting priority 0;
                oifname "wlan0" masquerade
        }
}
```

```configuration:/etc/sysctl.conf
...
(以下の行をアンコメント)
# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
...
```

```shell-session:NAPT開始
% sudo sysctl -p
% sudo systemctl enable nftables.service
% sudo systemctl start nftables.service
```


### DHCP server and DNS server using dnsmasq

```configuration:/etc/dnsmasq.conf
domain-needed
bogus-priv
interface=eth0
no-hosts
dhcp-range=192.168.20.2,192.168.20.127,12h
(おしり二行なんかもとからついてるのでそれはそのまま使ってる)
(ipアドレスくばる*先*がraspberry piでなければおしり二行はたぶん消して大丈夫)
```

```shell-session:dhcpサーバーとdnsサーバーを起動
% sudo systemctl enable dnsmasq.service
% sudo systemctl start dnsmasq.service
```

### AirPlay

```configuration:/etc/shairport-sync.conf
general =
{
        audio_backend_buffer_desired_length_in_seconds = 1.0;
}
alsa =
{
    output_device = "hw:1";
    output_format = "S32";
}
dsp =
{
        convolution = "no";
        loudness = "no";
}
```

```shell-session:shareport-syncサービス立ち上げ
% sudo systemctl enable shairport-sync.service
% sudo systemctl start shairport-sync.service
```

## まだできてないこと

- BabyfaceのPhoneアウトボリュームがshairplay-syncから制御・同期できない
  - ソフトでボリュームの変更はできるが質が多分悪い
- wifiというかsupplicantがDEAUTH_LEAVINGとかいう理由でなんか切れてるっぽい？のをdmesgで確認したので何の問題なのか一切わからないけどぐぐってなんかの名前を変更？して問題が解決したっぽかったが勘違いだった...まだ修正しきれておらず

## おまけ
Ansibleの動作が遅くて激怒していたが、どうもそういうものらしい。
wsl2 dockerコンテナから

- % time ssh raspberrypi echoした場合、real: 0.8s, user: 0.05s, sys: 0.00s
- % time ansible raspberrypi -m ping -i inventoryした場合、real: 6.3s, user: 3.3s, sys: 0.18s

Ansibleのpingモジュール、io待ちとおぼしきものが6.3s - 3.3s = 3.0sくらい。
また、同コンテナから

- % ANSIBLE_KEEP_REMOTE_FILES=1 ansible raspberrypi -m ping -i inventory -vvv

した場合、raspberrypi側にAnsiballZ_ping.pyが残り、raspberry pi側で

- % time python3 ~/.ansible/tmp/ansible-tmp-ほにゃらら/AnsiballZ_ping.pyした場合、real: 2.5s, user: 2.3s, sys: 0.15s

io待ちが3.0sくらいに対してraspi側の実行が2.5sくらいなのでまぁ納得の数字。

ちなみに、time ansible ...したときのtime値は、2回目以降の実行の数字。sshのControlMasterとかいうの利用しているので一回目はもう少し普通に時間がかかるけど2回目以降は早い。

さらにちなみにAnsiballZ_ping.pyなんで遅いの？とか思ったけど中にBASE64とかそういうのでエンコードされたバイナリっぽいのが入っているのでたぶんそれの展開が遅い。たぶん。たしか100kbyteオーダーのファイルサイズあった。たぶん。pipeliningとかいうやつ用かなしらんけど。

## 参考にしたサイト
- https://qiita.com/homelan/items/62a09aa0d0586d0280a2
- https://unix.stackexchange.com/questions/386925/aborting-authentication-by-local-choice-reason-3-deauth-leaving-when-trying
- https://github.com/raspberrypi/linux/pull/1525
- https://www.angelcurio.com/raspberrypi/?Raspberry%20Pi/%E5%90%84%E7%A8%AE%E8%A8%AD%E5%AE%9A/log%2Ctmp%E3%81%AE%E3%82%AA%E3%83%B3%E3%83%A1%E3%83%A2%E3%83%AA%28tmpfs%29%E5%8C%96
- https://qiita.com/KEINOS/items/f3e6b3064b0cbe35fd03#initd-%E3%81%A7%E8%B5%B7%E5%8B%95%E6%99%82%E3%81%AB%E4%BB%BB%E6%84%8F%E3%81%AE%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B
- https://www.takosuke.net/systemd%E3%81%A7%E7%89%B9%E5%AE%9A%E3%81%AE%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E5%AE%8C%E4%BA%86%E3%82%92%E5%BE%85%E3%81%A3%E3%81%A6%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E8%B5%B7/
- https://iot-plus.net/make/raspi/extend-sdcard-lifetime-5plus1/
- https://www.angelcurio.com/raspberrypi/?Raspberry%20Pi/%E5%90%84%E7%A8%AE%E8%A8%AD%E5%AE%9A/log%2Ctmp%E3%81%AE%E3%82%AA%E3%83%B3%E3%83%A1%E3%83%A2%E3%83%AA%28tmpfs%29%E5%8C%96