2年間放置していたPwnagotchiをクリーンインストールして使えるようにするまで
はじめに
2年ほど放置していたPwnagotchiを、久しぶりに動かすことにした。
古い環境をそのまま直すのではなく、microSDを焼き直してクリーンインストールする。画面を動かして、SSHで入れて、バッテリーで持ち運べる状態に戻すのが今回の目標。
イメージを書き込めばすぐ終わるかと思ったら、USB接続でかなりハマった。最終的にはディスプレイとPiSugarの認識、SSHの鍵認証、PCからのインターネット共有までできたので、その記録を残す。
!
Pwnagotchiは無線ネットワークのセキュリティ検証に使うツール。検証は自分が管理するネットワークか、明確な許可を得た環境で行う。
今回はまず本体を使えるようにするところまでで、deauth・associate・advertiseは無効にしている。
今回の構成
項目
使用したもの
本体
Raspberry Pi Zero 2 W
ディスプレイ
Waveshare 2.13インチ V3・白黒
バッテリー
PiSugar 3
イメージ
jayofelony/pwnagotchi v2.9.5.8・64bit
SDカードの設定用
NVMe SSDから起動しているRaspberry Pi 5
普段の管理用PC
CachyOS・NetworkManager使用
使ったのは、こちらのPwnagotchi。
jayofelony/pwnagotchi
Pi 5はSSDから起動しているので、本体のmicroSDスロットが空いている。そこにZero用のカードを挿して、Pi 5へSSH接続して設定した。USBカードリーダーを別に用意せずに済んだ。
Pi 5を使うのはSDカードの準備まで。その後はZeroをCachyOSのPCへUSB接続して管理する。
!
以下のデバイス名やUSBインターフェース名は今回の実機のもの。特に/dev/mmcblk0を、確認せずに自分の環境へ当てはめないこと。
記事は今回の作業記録で、別のバージョンやディスプレイまで同じ手順で動くことを確認したものではない。
普通のRaspberry Pi OSではなく、専用イメージを使う
最初はZero 2 W向けのRaspberry Pi OS 64bitをSDカードに入れて、そこからPwnagotchiをインストールしようとしていた。
今回はそのルートをやめて、jayofelony版で配布している専用イメージを焼き直した。使用したファイルはこれ。
pwnagotchi-64bit-2.9.5.8.img.xz
Releasesから取得できる。ここでは使用した版を固定して書いているので、これから入れる場合はリリースの説明も確認してほしい。
Raspberry Pi Imagerを使う場合はカスタムイメージとして選択する。通常のRaspberry Pi OS用のユーザー名・Wi-Fi・SSH設定を、Imagerのカスタマイズでそのまま上書きしないようにする。
参考:公式Wikiのインストール手順
Pi 5でSDカードの設定をする
最初に起動ディスクとSDカードを区別する
Pi 5へSSH接続し、カードを挿した状態で確認する。
lsblk -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINTS
findmnt /
今回は以下の対応だった。
/dev/mmcblk0 Zero用のSDカード
├─mmcblk0p1 bootfs
└─mmcblk0p2 rootfs
/dev/nvme0n1 Pi 5の起動用SSD
├─nvme0n1p1 /boot/firmware
└─nvme0n1p2 /
bootfsとrootfsというラベルはSSD側にもある。ラベルだけで判断せず、デバイス名とマウント先を一緒に見る。
rootfsをマウントする
焼きたてのカードのLinux側を、Pi 5上にマウントした。
sudo mkdir -p /mnt/pwnagotchi
sudo mount /dev/mmcblk0p2 /mnt/pwnagotchi
ls -la /mnt/pwnagotchi/etc/pwnagotchi/
この時点では、/etc/pwnagotchi/にconfig.tomlはなかった。defaults.tomlもこのディレクトリにはない。
そこで、SDカード側にconfig.tomlを新しく作る。
sudo vi /mnt/pwnagotchi/etc/pwnagotchi/config.toml
/mnt/pwnagotchiを付け忘れるとPi 5自身の設定を触ってしまうので注意。
Waveshare V3とPiSugar 3の設定
最初に入れた設定は以下。
/etc/pwnagotchi/config.toml
[main]
name = "pwnagotchi"
lang = "en"
[personality]
deauth = false
associate = false
advertise = false
[ui.display]
enabled = true
type = "waveshare_3"
rotation = 180
[ui.web]
enabled = false
[main.plugins.pisugarx]
enabled = true
lowpower_shutdown = true
lowpower_shutdown_level = 10
[main.plugins.grid]
enabled = false
report = false
[main.plugins.auto-update]
enabled = false
Waveshareの設定は製品の世代で変わる。今回の2.13インチV3ではwaveshare_3を使った。
Web UIはまだ使わないので無効。外部へのレポート送信や自動更新も、初期設定では無効にしておいた。
PiSugarはpisugarxで残量表示と低残量時の終了処理を設定する。ただし、このプラグインを有効にしただけで、電源ボタンによる安全終了まで設定できるわけではなかった。ボタンの話は後半に書く。
I2CとSPIはすでに有効だった
SDカードのbootfsにあるconfig.txtを確認すると、今回のイメージには以下が入っていた。
bootfs/config.txt(抜粋)
[all]
dtparam=i2c1=on
dtparam=i2c_arm=on
dtparam=spi=on
gpu_mem=1
dtoverlay=dwc2
enable_uart=1
同じ行を追加する必要はなかった。すでに書かれている内容を確認してから編集する。
設定後は書き込みを同期し、rootfsとbootfsの両方をアンマウントしてからカードを抜いた。
cd ~
sync
sudo umount /mnt/pwnagotchi
bootfsが以下へ自動マウントされている場合
sudo umount "/media/$USER/bootfs"
lsblk -o NAME,SIZE,MOUNTPOINTS
SDカードの両パーティションのマウント先が空になっていれば、Zeroへ移せる。
初回起動。緑LEDは点滅するが、画面は動かない
ZeroへSDカードを入れ、PiSugarで電源を入れた。
最初は緑LEDが点滅するだけで、Waveshareの表示が変わらない。画面の設定を間違えたかと思ったが、待っていたら更新された。その後、AUTO表示になった。
後からログを見ると、ディスプレイとバッテリーの初期化は通っていた。
PiSugar3 is connected
initializing waveshare v2in13_V3 display
PiSugar3 is ready
initializing waveshare v3 display done
entering auto mode ...
今回のログには、次の警告もあった。
ui.fps is 0, the display will only update for major changes
画面が常に書き換わる設定ではないので、更新が止まって見えるだけでハングしたと決めつけない方がよかった。
なお、AUTOは動作モードの表示。これだけでdeauthなどが有効かどうかは判断できない。設定とログを確認する。今回確認した範囲ではdeauths=0、assocs=0だった。
一番ハマったのはUSB接続
ZeroのUSB接続には、本体のUSB端子を使う。PWR INやPiSugar側の充電端子ではなく、mini HDMI端子に近い方のmicro USB端子。
PC側では以下を確認した。
lsusb
ip -br addr
nmcli device status
journalctl -k --since '5 minutes ago' --no-pager
一度認識しても、通信できるとは限らない
最初はUSB機器の認識でエラーになった。
device descriptor read/64, error -110
その後、次の名前で認識されるようになった。
Raspberry Pi USB Gadget
ネットワークインターフェースも現れた。
enp0s20f0u3
ところが、これでもSSHには入れない。さらにホスト側のカーネルログに、こんなエラーが出た。
NETDEV WATCHDOG: ... transmit queue 0 timed out
抜き差しすると、またdevice descriptor read/64, error -110へ戻る。別のUSBポートでも同じエラーになった。
ケーブルや端子を疑うのは自然だけど、ここでは「SSHが無効なのか」「IPアドレスが違うのか」「そもそもUSB通信が止まっているのか」を分けて調べる必要があった。
SSHを有効にする設定をいじっても、USB機器の認識段階で止まっていたら接続できない。
SDカード側でSSHの設定を確認する
一度電源を切り、SDカードをPi 5へ戻して設定を確認した。
強制電源断後の調査では、いきなり書き込み可能にせず、読み取り専用でマウントした。
sudo mount -o ro,noload /dev/mmcblk0p2 /mnt/pwnagotchi
noloadはext4のジャーナル再生も抑止するための指定。ただし、未回復の状態を見ることになるので、これを修復完了と考えたり、そのまま書き込み用に使ったりはしない。
サービスの自動起動設定は、Pi 5上からSDカード側を指定して確認できる。
sudo systemctl --root=/mnt/pwnagotchi is-enabled \
ssh.service ssh.socket rpi-usb-gadget-ics.service
結果は以下。
enabled
disabled
enabled
ssh.serviceは有効。サービス方式なので、ssh.socketが無効なだけでは異常ではない。SSHのホスト鍵も存在し、openssh-serverとrpi-usb-gadgetもインストール済みだった。
ただし、enabledは自動起動する設定というだけで、実際に正常起動した証拠ではない。
USBドライバーが固まる報告と症状が一致していた
調べると、Raspberry PiのUSB Gadget側にかなり近い報告があった。
rpi-usb-gadget#31
Linux側の送信タイムアウトに続いて、抜き差し後のUSB認識まで失敗する、という流れが今回と似ている。提案されているのは、固まった状態を検知してg_etherを読み込み直す復旧処理。
ただし、確認時点では未マージのPRだった。今回このパッチを導入したわけではないし、Zero側のカーネルログまで取れていないので、これが原因だと確定したわけでもない。
今回接続できたのは、Zeroを完全に電源オフにしてから、PiSugarで起動し、AUTO表示になった後でPCへUSB接続し直したときだった。PC側の固定IP設定と組み合わせると、pingとSSHの応答が返ってきた。
!
調査中はSSHもローカルコンソールも使えず、強制電源断が必要になった。稼働中の電源断にはファイルシステム破損のリスクがあるので、通常の終了方法としては勧めない。SSHが使えるようになってからは、OSをシャットダウンして止める。
SSH接続は10.0.0.2ではなかった
古いPwnagotchiの解説で見かけるアドレスを、そのまま使わない方がよかった。
今回のイメージにはrpi-usb-gadgetが入っており、Zero側に以下の接続プロファイルがあった。
USB Gadget (shared)
USB Gadget (client)
shared側の設定はこうなっていた。
[ipv4]
address1=10.12.194.1/28
method=shared
そこで、まずPC側を同じサブネットの固定IPにした。
機器
IPアドレス
Zero
10.12.194.1/28
PC
10.12.194.2/28
以下はPC側で、新しくSSH専用の接続設定を作る場合の例。enp0s20f0u3は自分のUSBインターフェース名に置き換える。
nmcli connection add type ethernet \
ifname enp0s20f0u3 \
con-name pwnagotchi-usb \
connection.autoconnect yes \
ipv4.method manual \
ipv4.addresses 10.12.194.2/28 \
ipv4.never-default yes \
ipv6.method disabled
nmcli connection up pwnagotchi-usb
ipv4.never-default yesを付けて、PCの普段のインターネット接続がUSB側へ切り替わらないようにした。
これで、ようやく接続できた。
ping -c 2 10.12.194.1
ssh pi@10.12.194.1
初期ログイン後はpasswdでパスワードを変更しておく。
nmcliに「pwnagotchi」が出ないと思ったら
あとで電源を入れ直した際、nmcli deviceの一覧にPwnagotchiがいないように見えた。
実際には、デバイス名がenp0s20f0u3で、接続プロファイル名がpwnagotchi-usbだっただけ。さらに、切り分け中に自動接続を無効にしたままにしていた。
nmcli connection modify pwnagotchi-usb connection.autoconnect yes
nmcli connection up pwnagotchi-usb
接続後の該当行はこうなる。
enp0s20f0u3 ethernet 接続済み pwnagotchi-usb
USB認識に待ち時間があることと、自動接続の設定は別の話だった。
なお、今回はUSBポートに依存するインターフェース名に設定をひも付けている。挿すポートを変えた場合は、同じ名前で見えているか確認する。
SSHは公開鍵認証にする
毎回パスワードを打つのも面倒なので、PC側にある公開鍵を登録した。
これはZeroの中ではなく、接続元のPCで実行する。
ssh-copy-id -i ~/.ssh/id_ed25519.pub pi@10.12.194.1
今回は既存の鍵があったのでそれを使った。まだ鍵がない場合は、先に用途に合った鍵を作成する。
パスワード認証を使わずに入れるかも確認する。
ssh -o PasswordAuthentication=no \
-o KbdInteractiveAuthentication=no \
pi@10.12.194.1
これで入れれば、通常は次だけでよくなる。
ssh pi@10.12.194.1
秘密鍵のパスフレーズと、Zeroのログインパスワードは別物。秘密鍵にパスフレーズを付けている場合は、必要に応じてSSHエージェントを使う。
PiSugarの電源ボタンも設定した
バッテリー残量が画面に出たので、PiSugarはもう設定済みだと思っていた。しかし、pisugarxでの残量表示と、電源ボタンを押したときの安全終了は別だった。
Zeroには公式のPiSugar管理サービスが入っていなかったため、以下のプロジェクトのpisugar-serverとpisugar-poweroffを追加した。使用した版はv2.3.5、アーキテクチャはarm64。
PiSugar/pisugar-power-manager-rs
この段階ではまだZero自身にインターネット接続がなかったので、PCでパッケージを取得し、SSHで転送してインストールした。
今回はWeb UIやTCP APIを公開する必要がない。管理サービスはローカルのUnixソケットだけを使う設定にした。
--model 'PiSugar 3'
--config /etc/pisugar-server/config.json
--uds /tmp/pisugar-server.sock
ソフト電源オフに関係する設定は以下。
/etc/pisugar-server/config.json(抜粋)
{
"soft_poweroff": true,
"soft_poweroff_shell": "/usr/bin/systemctl poweroff"
}
PiSugarの電源ボタンでOSの終了を要求する設定で、カスタムボタンの長押しコールバックとは区別する。
実機ではサービスがPiSugar 3を認識し、ソフト電源オフが有効になっていることをAPIから確認できた。ボタンでの電源オフ・オンも試せた。
給電を切るタイミングは、まだ確認を残している
Pwnagotchiのログなどはzram上にあるため、OSの終了処理より先に給電を切りたくない。
今回のローカル設定では、パッケージに付属するpisugar-poweroff.serviceは無効にし、代わりにsystemdの最終シャットダウン段階のフックから公式のpisugar-poweroffバイナリを呼ぶ形にした。再起動では給電を切らず、poweroff・halt時だけ実行する。
これは今回の環境向けに追加した設定で、公式パッケージの標準状態そのままではない。ボタンで停止・起動できたことは確認したが、前回起動のjournalを取得できず、終了処理から給電停止までの順序をログで検証するところは残っている。
そのため、ここでは「安全終了の全手順を検証できた」とまでは書かない。ログ保存設定も追加したが、再起動をまたいで残ることは別途確認が必要だった。
電子ペーパーは電源を切っても表示が残るので、画面が消えたかどうかだけで電源状態を判断しない。
PCのWi-FiをUSB経由で共有する
SSHできるようになった時点では、Zeroから外のインターネットには出られなかった。USB内の固定IPで通信できることと、インターネットへ出る経路があることは別。
時計の同期や、必要なツールを取得するためにPCのWi-Fiを共有することにした。
最終的な構成はこれ。
SSHアドレスは変えず、PC側をNetworkManagerの共有モードにし、Zero側にはPCをデフォルトゲートウェイとして設定した。
!
以下は前の節でSSH接続できた状態からの変更。ネットワークの切り替えでSSHが切れる可能性があるので、元の接続設定をバックアップしてから行う。インターフェース名・接続プロファイル名は環境に合わせる。
自動切り替えを止め、Zero側に固定の接続設定を作る
rpi-usb-gadget-ics.serviceは、PCからの共有を検出してZero側の接続モードを切り替えるサービス。今回は固定アドレスで運用するため、この自動切り替えを止めた。USBドライバーのg_ether自体を無効にするわけではない。
以下はZero側で実行する。
sudo systemctl disable --now rpi-usb-gadget-ics.service
sudo nmcli connection add type ethernet \
ifname usb0 \
con-name "USB Laptop" \
connection.autoconnect yes \
connection.autoconnect-priority 200 \
ipv4.method manual \
ipv4.addresses 10.12.194.1/28 \
ipv4.gateway 10.12.194.2 \
ipv4.dns "8.8.8.8,1.1.1.1" \
ipv4.never-default no \
ipv4.route-metric 100 \
ipv6.method disabled
sudo nmcli connection modify "USB Gadget (shared)" connection.autoconnect no
sudo nmcli connection modify "USB Gadget (client)" connection.autoconnect no
ここではまだ新しい接続を有効化せず、先にPC側の準備をする。
PC側を共有モードに変更する
こちらはCachyOSのPC側で実行する。
nmcli connection modify pwnagotchi-usb \
ipv4.method shared \
ipv4.addresses 10.12.194.2/28 \
ipv4.never-default yes \
ipv6.method disabled
nmcli connection up pwnagotchi-usb
PC側のnever-defaultは引き続きyes。PC自身は普段どおりWi-Fiをデフォルトルートにする。
また、自分のPCではUFWが有効で、転送はデフォルト拒否だった。全体を無効化せず、ZeroからWi-Fiへ出る通信だけを許可した。
enp0s20f0u3: USB側、wlan0: インターネットへ出るWi-Fi側
sudo ufw route allow \
in on enp0s20f0u3 out on wlan0 \
from 10.12.194.1 to any \
comment 'Pwnagotchi USB internet'
これはUFWを使っていた今回の環境向けの設定。他のファイアウォールへそのまま当てはめるものではない。
最後にZero側で新しい接続を有効化する。
sudo nmcli connection up "USB Laptop"
Zero側の経路はこうなった。
default via 10.12.194.2 dev usb0 proto static metric 100
自動更新はプラグイン設定以外にもあった
ネットへ出す前に確認したところ、config.tomlで自動更新プラグインを無効にしていても、別のauto-update.timerが有効だった。
systemctl cat auto-update.service auto-update.timer
今回はこのタイマーも止めた。
sudo systemctl disable --now auto-update.timer
とりあえずネットにつながったからといって、apt upgradeを実行したわけではない。専用イメージの更新は、使っている版の手順を確認して行う。
ネットにつながっても、時計が合わずHTTPSで失敗した
共有後、名前解決はできるようになった。しかしGitHubへHTTPS接続するとエラーになる。
SSL certificate problem: certificate is not yet valid
Zeroの時計を確認すると、8月18日のままだった。実際の作業は10月なので、証明書をまだ有効になっていないものと判定していた。
curl -kで証明書チェックを無効にするのではなく、まずPCの現在時刻を使ってZeroの時計を補正し、systemd-timesyncdを再起動した。
その後はNTPの同期も確認できた。
timedatectl show -p NTPSynchronized
NTPSynchronized=yes
HTTPSも証明書チェックを有効にしたままで通った。
curl -4 -I github.com
HTTP/2 200
Debianの配布サイトへのHTTPS接続も200で返ってきた。DNSだけでなく、実際のHTTPS通信まで確認できてようやく共有完了。
今日はここまで
今回できるようになったことは以下。
Waveshare V3への表示
PiSugar 3の認識と残量表示
PCからのUSB接続・SSH公開鍵認証
同じUSBポートでの自動接続
USB経由のインターネット共有と時刻同期
PiSugarのソフト電源オフ設定と、ボタンでの停止・起動の確認
USB不調の根本原因と、PiSugarの終了シーケンスのログ確認はまだ残っている。特にUSBは、今回つながったから今後絶対に固まらない、とまでは言えない。
それでも、PCにつないでssh pi@10.12.194.1で入れるようになると、かなり触りやすくなった。しばらくはこの構成で様子を見ようと思う。
2年ぶりに小さい画面が動いているのを見ると、もう少し早くやればよかったなと思う。今日はここまで。
参考
jayofelony/pwnagotchi
インストール手順
USB接続の手順
Pwnagotchiの設定
Raspberry Pi USB Gadget
USB endpoint lockupに対する復旧処理の提案
PiSugar Power Manager
Mentions
2年間放置していたPwnagotchiをクリーンインストールして使えるようにするまで はじめに 2年ほど放置していたPwnagotchiを、久しぶりに動かすことにした。 古い環境をそのまま直すのではなく、microSDを焼き直してクリーンインストールする。画面を動かして、SSHで入れて、バッテリーで持ち運べる状態に戻すのが今回の目標。 イメージを書き込めばすぐ終わるかと思ったら、USB接続でかなりハマった。最終的にはディスプレイとPiSugarの認識、SSHの鍵認証、PCからのインターネット共有までできたので、その記録を残す。 ! Pwnagotchiは無線ネットワークのセキュリティ検証に使うツール。検証は自分が管理するネットワークか、明確な許可を得た環境で行う。 今回はまず本体を使えるようにするところまでで、deauth・associate・advertiseは無効にしている。 今回の構成 項目 使用したもの 本体 Raspberry Pi Zero 2 W ディスプレイ Waveshare 2.13インチ V3・白黒 バッテリー PiSugar 3 イメージ jayofelony/pwnagotchi v2.9.5.8・64bit SDカードの設定用 NVMe SSDから起動しているRaspberry Pi 5 普段の管理用PC CachyOS・NetworkManager使用 使ったのは、こちらのPwnagotchi。 jayofelony/pwnagotchi Pi 5はSSDから起動しているので、本体のmicroSDスロットが空いている。そこにZero用のカードを挿して、Pi 5へSSH接続して設定した。USBカードリーダーを別に用意せずに済んだ。 Pi 5を使うのはSDカードの準備まで。その後はZeroをCachyOSのPCへUSB接続して管理する。 ! 以下のデバイス名やUSBインターフェース名は今回の実機のもの。特に/dev/mmcblk0を、確認せずに自分の環境へ当てはめないこと。 記事は今回の作業記録で、別のバージョンやディスプレイまで同じ手順で動くことを確認したものではない。 普通のRaspberry Pi OSではなく、専用イメージを使う 最初はZero 2 W向けのRaspberry Pi OS 64bitをSDカードに入れて、そこからPwnagotchiをインストールしようとしていた。 今回はそのルートをやめて、jayofelony版で配布している専用イメージを焼き直した。使用したファイルはこれ。 pwnagotchi-64bit-2.9.5.8.img.xz Releasesから取得できる。ここでは使用した版を固定して書いているので、これから入れる場合はリリースの説明も確認してほしい。 Raspberry Pi Imagerを使う場合はカスタムイメージとして選択する。通常のRaspberry Pi OS用のユーザー名・Wi-Fi・SSH設定を、Imagerのカスタマイズでそのまま上書きしないようにする。 参考:公式Wikiのインストール手順 Pi 5でSDカードの設定をする 最初に起動ディスクとSDカードを区別する Pi 5へSSH接続し、カードを挿した状態で確認する。 lsblk -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINTS findmnt / 今回は以下の対応だった。 /dev/mmcblk0 Zero用のSDカード ├─mmcblk0p1 bootfs └─mmcblk0p2 rootfs /dev/nvme0n1 Pi 5の起動用SSD ├─nvme0n1p1 /boot/firmware └─nvme0n1p2 / bootfsとrootfsというラベルはSSD側にもある。ラベルだけで判断せず、デバイス名とマウント先を一緒に見る。 rootfsをマウントする 焼きたてのカードのLinux側を、Pi 5上にマウントした。 sudo mkdir -p /mnt/pwnagotchi sudo mount /dev/mmcblk0p2 /mnt/pwnagotchi ls -la /mnt/pwnagotchi/etc/pwnagotchi/ この時点では、/etc/pwnagotchi/にconfig.tomlはなかった。defaults.tomlもこのディレクトリにはない。 そこで、SDカード側にconfig.tomlを新しく作る。 sudo vi /mnt/pwnagotchi/etc/pwnagotchi/config.toml /mnt/pwnagotchiを付け忘れるとPi 5自身の設定を触ってしまうので注意。 Waveshare V3とPiSugar 3の設定 最初に入れた設定は以下。 /etc/pwnagotchi/config.toml [main] name = "pwnagotchi" lang = "en" [personality] deauth = false associate = false advertise = false [ui.display] enabled = true type = "waveshare_3" rotation = 180 [ui.web] enabled = false [main.plugins.pisugarx] enabled = true lowpower_shutdown = true lowpower_shutdown_level = 10 [main.plugins.grid] enabled = false report = false [main.plugins.auto-update] enabled = false Waveshareの設定は製品の世代で変わる。今回の2.13インチV3ではwaveshare_3を使った。 Web UIはまだ使わないので無効。外部へのレポート送信や自動更新も、初期設定では無効にしておいた。 PiSugarはpisugarxで残量表示と低残量時の終了処理を設定する。ただし、このプラグインを有効にしただけで、電源ボタンによる安全終了まで設定できるわけではなかった。ボタンの話は後半に書く。 I2CとSPIはすでに有効だった SDカードのbootfsにあるconfig.txtを確認すると、今回のイメージには以下が入っていた。 bootfs/config.txt(抜粋) [all] dtparam=i2c1=on dtparam=i2c_arm=on dtparam=spi=on gpu_mem=1 dtoverlay=dwc2 enable_uart=1 同じ行を追加する必要はなかった。すでに書かれている内容を確認してから編集する。 設定後は書き込みを同期し、rootfsとbootfsの両方をアンマウントしてからカードを抜いた。 cd ~ sync sudo umount /mnt/pwnagotchi bootfsが以下へ自動マウントされている場合 sudo umount "/media/$USER/bootfs" lsblk -o NAME,SIZE,MOUNTPOINTS SDカードの両パーティションのマウント先が空になっていれば、Zeroへ移せる。 初回起動。緑LEDは点滅するが、画面は動かない ZeroへSDカードを入れ、PiSugarで電源を入れた。 最初は緑LEDが点滅するだけで、Waveshareの表示が変わらない。画面の設定を間違えたかと思ったが、待っていたら更新された。その後、AUTO表示になった。 後からログを見ると、ディスプレイとバッテリーの初期化は通っていた。 PiSugar3 is connected initializing waveshare v2in13_V3 display PiSugar3 is ready initializing waveshare v3 display done entering auto mode ... 今回のログには、次の警告もあった。 ui.fps is 0, the display will only update for major changes 画面が常に書き換わる設定ではないので、更新が止まって見えるだけでハングしたと決めつけない方がよかった。 なお、AUTOは動作モードの表示。これだけでdeauthなどが有効かどうかは判断できない。設定とログを確認する。今回確認した範囲ではdeauths=0、assocs=0だった。 一番ハマったのはUSB接続 ZeroのUSB接続には、本体のUSB端子を使う。PWR INやPiSugar側の充電端子ではなく、mini HDMI端子に近い方のmicro USB端子。 PC側では以下を確認した。 lsusb ip -br addr nmcli device status journalctl -k --since '5 minutes ago' --no-pager 一度認識しても、通信できるとは限らない 最初はUSB機器の認識でエラーになった。 device descriptor read/64, error -110 その後、次の名前で認識されるようになった。 Raspberry Pi USB Gadget ネットワークインターフェースも現れた。 enp0s20f0u3 ところが、これでもSSHには入れない。さらにホスト側のカーネルログに、こんなエラーが出た。 NETDEV WATCHDOG: ... transmit queue 0 timed out 抜き差しすると、またdevice descriptor read/64, error -110へ戻る。別のUSBポートでも同じエラーになった。 ケーブルや端子を疑うのは自然だけど、ここでは「SSHが無効なのか」「IPアドレスが違うのか」「そもそもUSB通信が止まっているのか」を分けて調べる必要があった。 SSHを有効にする設定をいじっても、USB機器の認識段階で止まっていたら接続できない。 SDカード側でSSHの設定を確認する 一度電源を切り、SDカードをPi 5へ戻して設定を確認した。 強制電源断後の調査では、いきなり書き込み可能にせず、読み取り専用でマウントした。 sudo mount -o ro,noload /dev/mmcblk0p2 /mnt/pwnagotchi noloadはext4のジャーナル再生も抑止するための指定。ただし、未回復の状態を見ることになるので、これを修復完了と考えたり、そのまま書き込み用に使ったりはしない。 サービスの自動起動設定は、Pi 5上からSDカード側を指定して確認できる。 sudo systemctl --root=/mnt/pwnagotchi is-enabled \ ssh.service ssh.socket rpi-usb-gadget-ics.service 結果は以下。 enabled disabled enabled ssh.serviceは有効。サービス方式なので、ssh.socketが無効なだけでは異常ではない。SSHのホスト鍵も存在し、openssh-serverとrpi-usb-gadgetもインストール済みだった。 ただし、enabledは自動起動する設定というだけで、実際に正常起動した証拠ではない。 USBドライバーが固まる報告と症状が一致していた 調べると、Raspberry PiのUSB Gadget側にかなり近い報告があった。 rpi-usb-gadget#31 Linux側の送信タイムアウトに続いて、抜き差し後のUSB認識まで失敗する、という流れが今回と似ている。提案されているのは、固まった状態を検知してg_etherを読み込み直す復旧処理。 ただし、確認時点では未マージのPRだった。今回このパッチを導入したわけではないし、Zero側のカーネルログまで取れていないので、これが原因だと確定したわけでもない。 今回接続できたのは、Zeroを完全に電源オフにしてから、PiSugarで起動し、AUTO表示になった後でPCへUSB接続し直したときだった。PC側の固定IP設定と組み合わせると、pingとSSHの応答が返ってきた。 ! 調査中はSSHもローカルコンソールも使えず、強制電源断が必要になった。稼働中の電源断にはファイルシステム破損のリスクがあるので、通常の終了方法としては勧めない。SSHが使えるようになってからは、OSをシャットダウンして止める。 SSH接続は10.0.0.2ではなかった 古いPwnagotchiの解説で見かけるアドレスを、そのまま使わない方がよかった。 今回のイメージにはrpi-usb-gadgetが入っており、Zero側に以下の接続プロファイルがあった。 USB Gadget (shared) USB Gadget (client) shared側の設定はこうなっていた。 [ipv4] address1=10.12.194.1/28 method=shared そこで、まずPC側を同じサブネットの固定IPにした。 機器 IPアドレス Zero 10.12.194.1/28 PC 10.12.194.2/28 以下はPC側で、新しくSSH専用の接続設定を作る場合の例。enp0s20f0u3は自分のUSBインターフェース名に置き換える。 nmcli connection add type ethernet \ ifname enp0s20f0u3 \ con-name pwnagotchi-usb \ connection.autoconnect yes \ ipv4.method manual \ ipv4.addresses 10.12.194.2/28 \ ipv4.never-default yes \ ipv6.method disabled nmcli connection up pwnagotchi-usb ipv4.never-default yesを付けて、PCの普段のインターネット接続がUSB側へ切り替わらないようにした。 これで、ようやく接続できた。 ping -c 2 10.12.194.1 ssh pi@10.12.194.1 初期ログイン後はpasswdでパスワードを変更しておく。 nmcliに「pwnagotchi」が出ないと思ったら あとで電源を入れ直した際、nmcli deviceの一覧にPwnagotchiがいないように見えた。 実際には、デバイス名がenp0s20f0u3で、接続プロファイル名がpwnagotchi-usbだっただけ。さらに、切り分け中に自動接続を無効にしたままにしていた。 nmcli connection modify pwnagotchi-usb connection.autoconnect yes nmcli connection up pwnagotchi-usb 接続後の該当行はこうなる。 enp0s20f0u3 ethernet 接続済み pwnagotchi-usb USB認識に待ち時間があることと、自動接続の設定は別の話だった。 なお、今回はUSBポートに依存するインターフェース名に設定をひも付けている。挿すポートを変えた場合は、同じ名前で見えているか確認する。 SSHは公開鍵認証にする 毎回パスワードを打つのも面倒なので、PC側にある公開鍵を登録した。 これはZeroの中ではなく、接続元のPCで実行する。 ssh-copy-id -i ~/.ssh/id_ed25519.pub pi@10.12.194.1 今回は既存の鍵があったのでそれを使った。まだ鍵がない場合は、先に用途に合った鍵を作成する。 パスワード認証を使わずに入れるかも確認する。 ssh -o PasswordAuthentication=no \ -o KbdInteractiveAuthentication=no \ pi@10.12.194.1 これで入れれば、通常は次だけでよくなる。 ssh pi@10.12.194.1 秘密鍵のパスフレーズと、Zeroのログインパスワードは別物。秘密鍵にパスフレーズを付けている場合は、必要に応じてSSHエージェントを使う。 PiSugarの電源ボタンも設定した バッテリー残量が画面に出たので、PiSugarはもう設定済みだと思っていた。しかし、pisugarxでの残量表示と、電源ボタンを押したときの安全終了は別だった。 Zeroには公式のPiSugar管理サービスが入っていなかったため、以下のプロジェクトのpisugar-serverとpisugar-poweroffを追加した。使用した版はv2.3.5、アーキテクチャはarm64。 PiSugar/pisugar-power-manager-rs この段階ではまだZero自身にインターネット接続がなかったので、PCでパッケージを取得し、SSHで転送してインストールした。 今回はWeb UIやTCP APIを公開する必要がない。管理サービスはローカルのUnixソケットだけを使う設定にした。 --model 'PiSugar 3' --config /etc/pisugar-server/config.json --uds /tmp/pisugar-server.sock ソフト電源オフに関係する設定は以下。 /etc/pisugar-server/config.json(抜粋) { "soft_poweroff": true, "soft_poweroff_shell": "/usr/bin/systemctl poweroff" } PiSugarの電源ボタンでOSの終了を要求する設定で、カスタムボタンの長押しコールバックとは区別する。 実機ではサービスがPiSugar 3を認識し、ソフト電源オフが有効になっていることをAPIから確認できた。ボタンでの電源オフ・オンも試せた。 給電を切るタイミングは、まだ確認を残している Pwnagotchiのログなどはzram上にあるため、OSの終了処理より先に給電を切りたくない。 今回のローカル設定では、パッケージに付属するpisugar-poweroff.serviceは無効にし、代わりにsystemdの最終シャットダウン段階のフックから公式のpisugar-poweroffバイナリを呼ぶ形にした。再起動では給電を切らず、poweroff・halt時だけ実行する。 これは今回の環境向けに追加した設定で、公式パッケージの標準状態そのままではない。ボタンで停止・起動できたことは確認したが、前回起動のjournalを取得できず、終了処理から給電停止までの順序をログで検証するところは残っている。 そのため、ここでは「安全終了の全手順を検証できた」とまでは書かない。ログ保存設定も追加したが、再起動をまたいで残ることは別途確認が必要だった。 電子ペーパーは電源を切っても表示が残るので、画面が消えたかどうかだけで電源状態を判断しない。 PCのWi-FiをUSB経由で共有する SSHできるようになった時点では、Zeroから外のインターネットには出られなかった。USB内の固定IPで通信できることと、インターネットへ出る経路があることは別。 時計の同期や、必要なツールを取得するためにPCのWi-Fiを共有することにした。 最終的な構成はこれ。 SSHアドレスは変えず、PC側をNetworkManagerの共有モードにし、Zero側にはPCをデフォルトゲートウェイとして設定した。 ! 以下は前の節でSSH接続できた状態からの変更。ネットワークの切り替えでSSHが切れる可能性があるので、元の接続設定をバックアップしてから行う。インターフェース名・接続プロファイル名は環境に合わせる。 自動切り替えを止め、Zero側に固定の接続設定を作る rpi-usb-gadget-ics.serviceは、PCからの共有を検出してZero側の接続モードを切り替えるサービス。今回は固定アドレスで運用するため、この自動切り替えを止めた。USBドライバーのg_ether自体を無効にするわけではない。 以下はZero側で実行する。 sudo systemctl disable --now rpi-usb-gadget-ics.service sudo nmcli connection add type ethernet \ ifname usb0 \ con-name "USB Laptop" \ connection.autoconnect yes \ connection.autoconnect-priority 200 \ ipv4.method manual \ ipv4.addresses 10.12.194.1/28 \ ipv4.gateway 10.12.194.2 \ ipv4.dns "8.8.8.8,1.1.1.1" \ ipv4.never-default no \ ipv4.route-metric 100 \ ipv6.method disabled sudo nmcli connection modify "USB Gadget (shared)" connection.autoconnect no sudo nmcli connection modify "USB Gadget (client)" connection.autoconnect no ここではまだ新しい接続を有効化せず、先にPC側の準備をする。 PC側を共有モードに変更する こちらはCachyOSのPC側で実行する。 nmcli connection modify pwnagotchi-usb \ ipv4.method shared \ ipv4.addresses 10.12.194.2/28 \ ipv4.never-default yes \ ipv6.method disabled nmcli connection up pwnagotchi-usb PC側のnever-defaultは引き続きyes。PC自身は普段どおりWi-Fiをデフォルトルートにする。 また、自分のPCではUFWが有効で、転送はデフォルト拒否だった。全体を無効化せず、ZeroからWi-Fiへ出る通信だけを許可した。 enp0s20f0u3: USB側、wlan0: インターネットへ出るWi-Fi側 sudo ufw route allow \ in on enp0s20f0u3 out on wlan0 \ from 10.12.194.1 to any \ comment 'Pwnagotchi USB internet' これはUFWを使っていた今回の環境向けの設定。他のファイアウォールへそのまま当てはめるものではない。 最後にZero側で新しい接続を有効化する。 sudo nmcli connection up "USB Laptop" Zero側の経路はこうなった。 default via 10.12.194.2 dev usb0 proto static metric 100 自動更新はプラグイン設定以外にもあった ネットへ出す前に確認したところ、config.tomlで自動更新プラグインを無効にしていても、別のauto-update.timerが有効だった。 systemctl cat auto-update.service auto-update.timer 今回はこのタイマーも止めた。 sudo systemctl disable --now auto-update.timer とりあえずネットにつながったからといって、apt upgradeを実行したわけではない。専用イメージの更新は、使っている版の手順を確認して行う。 ネットにつながっても、時計が合わずHTTPSで失敗した 共有後、名前解決はできるようになった。しかしGitHubへHTTPS接続するとエラーになる。 SSL certificate problem: certificate is not yet valid Zeroの時計を確認すると、8月18日のままだった。実際の作業は10月なので、証明書をまだ有効になっていないものと判定していた。 curl -kで証明書チェックを無効にするのではなく、まずPCの現在時刻を使ってZeroの時計を補正し、systemd-timesyncdを再起動した。 その後はNTPの同期も確認できた。 timedatectl show -p NTPSynchronized NTPSynchronized=yes HTTPSも証明書チェックを有効にしたままで通った。 curl -4 -I github.com HTTP/2 200 Debianの配布サイトへのHTTPS接続も200で返ってきた。DNSだけでなく、実際のHTTPS通信まで確認できてようやく共有完了。 今日はここまで 今回できるようになったことは以下。 Waveshare V3への表示 PiSugar 3の認識と残量表示 PCからのUSB接続・SSH公開鍵認証 同じUSBポートでの自動接続 USB経由のインターネット共有と時刻同期 PiSugarのソフト電源オフ設定と、ボタンでの停止・起動の確認 USB不調の根本原因と、PiSugarの終了シーケンスのログ確認はまだ残っている。特にUSBは、今回つながったから今後絶対に固まらない、とまでは言えない。 それでも、PCにつないでssh pi@10.12.194.1で入れるようになると、かなり触りやすくなった。しばらくはこの構成で様子を見ようと思う。 2年ぶりに小さい画面が動いているのを見ると、もう少し早くやればよかったなと思う。今日はここまで。 参考 jayofelony/pwnagotchi インストール手順 USB接続の手順 Pwnagotchiの設定 Raspberry Pi USB Gadget USB endpoint lockupに対する復旧処理の提案 PiSugar Power Manager