Raspberry PIでNTPの同期が取れてからアプリケーションのサービスを起動するようにした
Raspberry PIにはRTC(リアルタイムクロック)が搭載されていないので、ボード上に日時を保存しておいてOSのシステム時計に反映する方法はデフォルトではない。
ただ、昨今のRaspbianはsystemd-timesyncdによって、ファイルシステムに最後の日時を保存しておいてそこから復元し、その後NTPで同期するみたいな動きになっている。なので、OSを起動すると毎回とりあえず1970年から開始…みたいな感じにはならない。
systemd-timesyncd はネットワークを介してシステム時刻を同期させるために追加されたデーモンです。 Raspberry Pi や組み込みデバイスなどの RTC を載せてないシステムのために、新しい NTP の同期が取得される度にディスクに現在の時刻を保存し、それを使って起動時にシステム時刻を修正することができ、時刻が常に正しいわけではないときでも、それらのシステムで時刻がモノトニックに進むことを保証します。
ただし、これだと自分で作ったアプリケーションをいつ起動するのかによってシステム時刻の正確な日時が取れるタイミングが微妙な感じになる。
サンプルアプリケーションで検証してみる
たとえば、次のようなアプリケーションを用意する。起動すると1秒おきに現在時刻をログファイルに記録する仕様になっている。
from datetime import datetime from logging import getLogger, basicConfig, DEBUG import time logger = getLogger(__name__) basicConfig(level=DEBUG, format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', filename='./app.log') try: while True: logger.info("current:%s" % datetime.now().strftime("%Y-%m-%d %H:%M:%S")) time.sleep(1) except KeyboardInterrupt: pass
これを起動順は指定せずに、単純にsystemdにサービスとして登録する。systemd用の設定ファイルを/etc/systemd/system/myapp.service
として次のように用意する。
[Unit] Description = My Application [Service] ExecStart = /home/pi/app/venv/bin/python app.py WorkingDirectory = /home/pi/app User=pi Restart=always Type=simple [Install] WantedBy = multi-user.target
これをsystemctl
で有効化する。
$ sudo systemctl enable myapp.service
OSを起動してアプリケーションが起動すると次のようなログができあがる。
2019-08-14 11:25:04,274 __main__ INFO current:2019-08-14 11:25:04 2019-08-14 11:25:05,278 __main__ INFO current:2019-08-14 11:25:05 2019-08-14 11:25:06,282 __main__ INFO current:2019-08-14 11:25:06 2019-08-14 11:25:07,285 __main__ INFO current:2019-08-14 11:25:07 2019-08-14 11:25:08,288 __main__ INFO current:2019-08-14 11:25:08 2019-08-14 11:25:09,291 __main__ INFO current:2019-08-14 11:25:09 2019-08-14 11:25:10,294 __main__ INFO current:2019-08-14 11:25:10 *** 中略 *** 2019-08-14 11:25:25,405 __main__ INFO current:2019-08-14 11:25:25 2019-08-14 11:25:26,409 __main__ INFO current:2019-08-14 11:25:26 2019-08-14 11:25:27,412 __main__ INFO current:2019-08-14 11:25:27 2019-08-14 11:25:28,415 __main__ INFO current:2019-08-14 11:25:28 2019-08-14 11:25:29,418 __main__ INFO current:2019-08-14 11:25:29 2019-08-14 11:25:30,427 __main__ INFO current:2019-08-14 11:25:30 2019-08-14 11:27:48,380 __main__ INFO current:2019-08-14 11:27:48 2019-08-14 11:27:49,383 __main__ INFO current:2019-08-14 11:27:49 2019-08-14 11:27:50,386 __main__ INFO current:2019-08-14 11:27:50 2019-08-14 11:27:51,389 __main__ INFO current:2019-08-14 11:27:51 2019-08-14 11:27:52,398 __main__ INFO current:2019-08-14 11:27:52
ログのタイムスタンプを見ていくと、11:25:04に起動したアプリケーションがログを記録して行き、11:25:30のあたりでNTPによってシステム時計が調整されて、つぎから11:27:48として正確な時刻で記録されていく。
これだと、起動してからNTPの同期が完了するまでの間のdatetime.now()
やログのタイムスタンプはあてにならないことになる。
アプリケーションの起動をsystemd-timesyncdの後にしてみる
NTPの同期をするのがsystemd-timesyncdなので、アプリケーションのsystemdの設定を次のように、After=systemd-timesyncd.service
と起動順の指定をしてサービス登録する。
これでsystemd-timesyncdサービスが起動してからアプリケーションが起動するようになる。
[Unit] Description = My Application After=systemd-timesyncd.service [Service] ExecStart = /home/pi/app/venv/bin/python app.py WorkingDirectory = /home/pi/app User=pi Restart=always Type=simple [Install] WantedBy = multi-user.target
が、これで改めて記録したログを観察すると次のようになる。
2019-08-14 11:39:33,913 __main__ INFO current:2019-08-14 11:39:33 2019-08-14 11:39:34,917 __main__ INFO current:2019-08-14 11:39:34 2019-08-14 11:39:35,921 __main__ INFO current:2019-08-14 11:39:35 *** 中略 *** 2019-08-14 11:39:57,052 __main__ INFO current:2019-08-14 11:39:57 2019-08-14 11:39:58,055 __main__ INFO current:2019-08-14 11:39:58 2019-08-14 11:39:59,059 __main__ INFO current:2019-08-14 11:39:59 2019-08-14 11:40:00,063 __main__ INFO current:2019-08-14 11:40:00 2019-08-14 11:43:03,507 __main__ INFO current:2019-08-14 11:43:03 2019-08-14 11:43:04,510 __main__ INFO current:2019-08-14 11:43:04 2019-08-14 11:43:05,514 __main__ INFO current:2019-08-14 11:43:05 2019-08-14 11:43:06,517 __main__ INFO current:2019-08-14 11:43:06 2019-08-14 11:43:07,521 __main__ INFO current:2019-08-14 11:43:07
11:39:33に起動してログを取り始め、11:40:00のあたりでNTPの同期が反映されて、11:43:03から正確な時刻で記録されていく。
syslogみるとmyapp.serviceの起動より後にsystemd-timesyncdで時刻同期されているのがわかる。
Aug 14 11:39:31 raspberrypi systemd[1]: Started My Application. Aug 14 11:39:31 raspberrypi systemd[1]: Started Daily man-db regeneration. Aug 14 11:39:31 raspberrypi systemd[1]: Reached target Timers. Aug 14 11:39:31 raspberrypi systemd[1]: Started triggerhappy global hotkey daemon. Aug 14 11:39:31 raspberrypi systemd[1]: Started System Logging Service. *** 中略 *** Aug 14 11:43:02 raspberrypi systemd-timesyncd[274]: Synchronized to time server for the first time 133.243.238.243:123 (ntp.nict.jp).
これはsystemd-timesyncdの起動タイミングとNTPで同期されるタイミングが異なるためで、NTPが同期されたタイミングを正しく待つ必要がある。
NTPの同期が完了されるまで待ってアプリケーションを起動する
これを実現するためにwait-timesyncを作った。
systemd-timesyncdが同期したらタイムスタンプを更新する/var/lib/systemd/clock
ファイルの更新日時を監視するようになっている。
#!/bin/bash TARGET=${WAIT_CLOCK_TARGET:-"/var/lib/systemd/clock"} TIMEOUT=${WAIT_TIMEOUT:-60} CHANGE=0 LAST_TS=`stat -c %Y $TARGET` for i in `seq 1 $TIMEOUT` ; do TS=`stat -c %Y $TARGET` if [ $LAST_TS != $TS ]; then CHANGE=1 echo "timesyncd clock updated [$i sec waited]" break fi sleep 1 done if [ $CHANGE == 0 ]; then echo "timeout watch timesyncd clock update" fi
これを次の/etc/systemd/system/wait-timesync.service
でsystemdにサービス登録する。
[Unit] Description = Wait timesyncd After=systemd-timesyncd.service [Service] ExecStart = /usr/local/bin/wait-timesync EnvironmentFile = /etc/default/wait-timesync Type=oneshot [Install] WantedBy = multi-user.target
監視対象のclockファイルは場所が違う場合があるので、次のように環境変数で指定するようにしてある。同期待ちのタイムアウト(秒)も環境変数で指定する。これは、/etc/default/wait-timesync
ファイルとして設置し、参照する。
WAIT_CLOCK_TARGET=/var/lib/systemd/clock WAIT_TIMEOUT=60
これを利用してmyapp.serviceのsystemd設定を変更し、After=wait-timesync.service
として再設定する。
[Unit] Description = My Application After=wait-timesync.service [Service] ExecStart = /home/pi/app/venv/bin/python app.py WorkingDirectory = /home/pi/app User=pi Restart=always Type=simple [Install] WantedBy = multi-user.target
改めてOSを起動してログファイルを確認すると次のようになり、途中で同期されてタイムスタンプが変わることはなくなる。
2019-08-14 11:49:08,968 __main__ INFO current:2019-08-14 11:49:08 2019-08-14 11:49:09,971 __main__ INFO current:2019-08-14 11:49:09 2019-08-14 11:49:10,975 __main__ INFO current:2019-08-14 11:49:10 2019-08-14 11:49:11,980 __main__ INFO current:2019-08-14 11:49:11 2019-08-14 11:49:12,984 __main__ INFO current:2019-08-14 11:49:12 2019-08-14 11:49:13,989 __main__ INFO current:2019-08-14 11:49:13 2019-08-14 11:49:14,993 __main__ INFO current:2019-08-14 11:49:14 2019-08-14 11:49:15,997 __main__ INFO current:2019-08-14 11:49:15
このとき、syslogを見ると次のようになっている。
Aug 14 11:46:04 raspberrypi systemd[1]: Starting Wait timesyncd... Aug 14 11:46:04 raspberrypi systemd[1]: Starting OpenBSD Secure Shell server... Aug 14 11:46:04 raspberrypi systemd[1]: Starting Permit User Sessions... *** 中略 *** Aug 14 11:49:06 raspberrypi systemd-timesyncd[281]: Synchronized to time server for the first time 133.243.238.243:123 (ntp.nict.jp). Aug 14 11:49:07 raspberrypi wait-timesync[469]: timesyncd clock updated [21 sec waited] Aug 14 11:49:07 raspberrypi systemd[1]: wait-timesync.service: Succeeded. Aug 14 11:49:07 raspberrypi systemd[1]: Started Wait timesyncd. Aug 14 11:49:07 raspberrypi systemd[1]: Started My Application.
アプリケーションの起動がwait-timesyncの完了後になり、NTPの同期後になっていることがわかる。ちなみにNTPの同期にはwait-timesyncが起動してから21秒かかっていることが分かる。
アプリケーションの起動が待たされることにはなるけど、RTCをRaspberry PIに載せるまでは行かないけどタイムスタンプは正確にしときたいみたいな用途に使えると思う。
極力バックエンドを書かずに異なるドメインのWebサイトからデータを取得するCORS Proxyというのを作った
作ったと言っても、実際コードを書いたのは2年近く前だったのだけど。
当時書いてたアプリのために作っておいたのをずっと放置していたが、最近ちょっと使う用途があったので久しぶりに触ったら何もかも忘れてて(README書いてなかった)えらい苦労した。改めて使い方調べながらREADMEまとめたので、ついでに気づいたバグとか直してGitHubで公開した。
CORSとは
Cross-Origin Resource Sharing (CORS) は、追加の HTTP ヘッダーを使って、あるオリジン (ドメイン) で動いているウェブアプリケーションが、異なるオリジンのサーバーのリソースにアクセスできるようにする仕組み
雑に説明すると、サーバ側でAccess-Control-Allow-Origin
ヘッダーをレスポンスで返してなんやかんやするやつ。
今回作ったやつは追加のリクエストヘッダーをいくつか必要としているので、Access-Control-Allow-Headers
ヘッダーのレスポンスやプリフライトリクエスト(OPTIONSメソッドのリクエストが本命の前に実行される)への対応も必要になっている。
使い方
環境準備
Herokuで動かすようにしてあるので、リポジトリをクローンしたらHerokuアプリケーション作って、認証用の環境変数を設定してデプロイする。
$ git clone https://github.com/ushiboy/cors-proxy $ cd cors-proxy $ heroku create
認証用のキーはbin/generate_key
でランダムに生成する。
$ bin/generate_key [Key]: tV4VWOcq0HbJ... [Digest]: $2y$10$do7go...
認証用の環境変数はAUTH_KEY_DIGEST
に生成したDigest
の値を、ALLOW_ORIGIN
に許可するオリジン(複数指定する場合はカンマで区切る)を設定する。
$ heroku config:set AUTH_KEY_DIGEST='$2y$10$do7go...' ALLOW_ORIGIN=http://myapp.com
プッシュしてデプロイする。
$ git push heroku master
Webフロントエンド側からの利用
デプロイ先のHerokuアプリケーションのURLに、生成した認証用のKey
の方をAuthorization
リクエストヘッダーにBearer
としてつけて、クエリストリングのq
パラメータに取得対象のURLを指定してリクエストを発行する。
const q = encodeURIComponent('http://www.example.com/data.txt'); const result = await fetch(`https://<your_app_name>.herokuapp.com/?q=${q}`, { headers: { 'Authorization': `Bearer tV4VWOcq0HbJ...` } });
これでプリフライトリクエストのあとに正しく取得ができれば動作はOK。
取得対象が古のCP932とかCP51932とかUTF-8ではない場合はX-From-Charset
ヘッダーをつけてリクエストすることで、UTF-8にエンコードして取得できる。
const q = encodeURIComponent('http://www.example.com/data.txt'); const result = await fetch(`https://<your_app_name>.herokuapp.com/?q=${q}`, { headers: { 'Authorization': `Bearer tV4VWOcq0HbJ...`, 'X-From-Charset': 'CP51932' } });
X-From-Charset
に指定できる文字コードの種類はPHPのmbstringがサポートしてるやつ。
補足
認証キーとオリジンでアクセス制限するようにしているけど、これは気休め程度。Webフロントエンド以外のところでやろうと思えばどうにでもできるので、フロントエンドのコードにベタ書きして使うかは要検討。
自分はHerokuアプリケーションのURLと認証キーをローカルストレージに設定するようにしてコードにベタ書きはせずに使ってる。
GETしかできないものなので、そこまで気にしなくても良いかも...という気もしないでもない。
とりあえずちょっと外部Webサイトのリソースも使いたいWebアプリ書くとかだったら、フロントエンド側はGithub Pagesでホストしてそこから利用するとかできると思う。
Raspberry PI ZeroのUSB OTGで遊んだ
Raspberry PI Zeroのセットアップで、電源供給用じゃない方のマイクロUSBポートとホストPCを繋いで仮想ネットワーク経由で接続する云々の情報をみて、どういう仕組みでやっているんだろうと調べてみたら、Raspberry PI ZeroではUSB OTG(On-The-Go)が使えるということを知った。
USB OTG (On-The-Go)
USB OTGはAndroidのUSBポートにマウスとかキーボード繋げばマウスやキーボードとして動くけど、同じポートでPCに繋いだ時はPC側でストレージとして見えるみたいなアレっぽい。
USB On-The-Go(略してUSB OTG)は、USB機器どうしを直接接続するインタフェース規格である。パソコン等をホストとせずに、動作時にホスト機器を動的に切り替える機能を拡張したもの。 IEEE 1394のように直接接続できるので、いろいろな機器に応用できる。
調べてみたら手持ちのRaspberry PI B系では構造上できないようなので、Raspberry PI Zero WHを買ってみた。送料とか色々考えてとりあえずAmazonでポチリ。
Raspberry Pi Zero WH GPIOピンヘッダー ハンダ付け済み Wi-Fi & Bluetooth&ヒートシンク付
- 出版社/メーカー: Raspberrypi
- メディア: エレクトロニクス
- この商品を含むブログを見る
シリアル通信したり、ネットワークにしたり、キーボードにしたりと色々できるみたいだけど、とりあえず今回はストレージとして使う方法を試してみた。
USB OTGでマスストレージ
環境は以前に作成したRaspbian Stretch Liteの2019-04-08バージョンのパーティションをいじって、bootとrootfsの他にextrafsを追加したもの。今出ているBusterの2019-06-20バージョンはとりあえず保留。
環境準備
まずはUSB OTGを利用するために,/boot/config.txt
に次のUSB OTGの有効化を追加する。
dtoverlay=dwc2
反映するためにOSを再起動する。
続いて、ストレージとして利用するためのパーティションを作成する。イメージファイルとかでも良いらしいけど、今回は専用パーティションを作って行う。
現状のパーティションが次のとおり。
$ sudo fdisk -l /dev/mmcblk0 Disk /dev/mmcblk0: 7.4 GiB, 7932477440 bytes, 15493120 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0xc1dc39e5 Device Boot Start End Sectors Size Id Type /dev/mmcblk0p1 8192 96042 87851 42.9M c W95 FAT32 (LBA) /dev/mmcblk0p2 98304 6389759 6291456 3G 83 Linux /dev/mmcblk0p3 6389760 8388607 1998848 976M 83 Linux
これに1GBのFAT32パーティションを追加して次の様にした。
$ sudo fdisk -l /dev/mmcblk0 Disk /dev/mmcblk0: 7.4 GiB, 7932477440 bytes, 15493120 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0xc1dc39e5 Device Boot Start End Sectors Size Id Type /dev/mmcblk0p1 8192 96042 87851 42.9M c W95 FAT32 (LBA) /dev/mmcblk0p2 98304 6389759 6291456 3G 83 Linux /dev/mmcblk0p3 6389760 8388607 1998848 976M 83 Linux /dev/mmcblk0p4 8388608 10485759 2097152 1G b W95 FAT32
パーティションを切った後にOSを再起動して、vfatでフォーマットする。
$ sudo mkfs -t vfat /dev/mmcblk0p4
これでパーティションの準備は完了。
マスストレージの有効化と解除
環境を準備して次のコマンドを実行すると接続しているホストPC側にUSBストレージとして認識させることができる。 パラメータのオプションは linux/mass-storage.txt at master · torvalds/linux · GitHub を参考にした。
色々試していたらWindowsでUSBストレージとして認識させるにはremovable=y
が必要だった。
$ sudo modprobe g_mass_storage file=/dev/mmcblk0p4 removable=y
lsmodで確認すると次の様に表示される。
$ lsmod | grep g_mass_storage g_mass_storage 4651 0 usb_f_mass_storage 38658 2 g_mass_storage libcomposite 49673 2 g_mass_storage,usb_f_mass_storage
dmesgを確認すると次のようなログが確認できる。
[ 46.534849] Mass Storage Function, version: 2009/09/11 [ 46.534873] LUN: removable file: (no medium) [ 46.535068] LUN: removable file: /dev/mmcblk0p4 [ 46.535077] Number of LUNs=1 [ 46.537657] g_mass_storage gadget: Mass Storage Gadget, version: 2009/09/11 [ 46.537675] g_mass_storage gadget: userspace failed to provide iSerialNumber [ 46.537681] g_mass_storage gadget: g_mass_storage ready [ 46.537695] dwc2 20980000.usb: bound driver g_mass_storage [ 46.654687] dwc2 20980000.usb: new device is high-speed [ 47.346817] dwc2 20980000.usb: new device is high-speed [ 47.414698] dwc2 20980000.usb: new device is high-speed [ 47.632787] dwc2 20980000.usb: new address 1 [ 47.657761] g_mass_storage gadget: high-speed config #1: Linux File-Backed Storage
マスストレージを解除するには次のコマンドを実行する。
$ sudo modprobe -r g_mass_storage
OS起動時にマスストレージを有効にしてみる
準備が整ったので、OS起動時に自動的にマスストレージを有効にできるようにしてみる。
最初に思いつくのは単純な方法で/etc/rc.local
に起動コマンドを書いておくというもの。
/etc/rc.local
を使った方法
/etc/rc.local
を次の様に編集する。
#!/bin/sh -e # # rc.local # # This script is executed at the end of each multiuser runlevel. # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. # Print the IP address _IP=$(hostname -I) || true if [ "$_IP" ]; then printf "My IP address is %s\n" "$_IP" fi # for MASS Storage modprobe g_mass_storage file=/dev/mmcblk0p4 removable=y exit 0
これで再起動して動作確認。
dmesgでマスストレージが有効化されたタイミングを見てみる。
[ 25.047610] Mass Storage Function, version: 2009/09/11 [ 25.047631] LUN: removable file: (no medium) [ 25.047818] LUN: removable file: /dev/mmcblk0p4 [ 25.047827] Number of LUNs=1 [ 25.061717] g_mass_storage gadget: Mass Storage Gadget, version: 2009/09/11 [ 25.061738] g_mass_storage gadget: userspace failed to provide iSerialNumber [ 25.061744] g_mass_storage gadget: g_mass_storage ready [ 25.061758] dwc2 20980000.usb: bound driver g_mass_storage [ 25.179253] dwc2 20980000.usb: new device is high-speed [ 25.558496] dwc2 20980000.usb: new device is high-speed [ 25.684040] dwc2 20980000.usb: new device is high-speed [ 25.761661] dwc2 20980000.usb: new address 1 [ 25.785904] g_mass_storage gadget: high-speed config #1: Linux File-Backed Storage
/etc/rc.local
だとOS起動の最後のほうで動くので、25秒くらいかかってるのがわかる。
これだと、ホストPC側に繋いでからUSBストレージとして認識されるまでが結構待たされる気がする。
systemdでサービス化する方法
次にsystemdでoneshotなサービスを作って実行する方法でやってみる。
/etc/rc.local
は元に戻しておいて、/lib/systemd/system/storage.service
を次の様に作る。
[Unit] Description = MASS Storage Service After = local-fs.target [Service] ExecStart = /sbin/modprobe g_mass_storage file=/dev/mmcblk0p4 Type = oneshot [Install] WantedBy = multi-user.target
作ったサービスを有効化して、再起動する。
$ sudo systemctl enable storage.service
再びdmesgでマスストレージが有効化されたタイミングを見てみる。
[ 15.075042] Mass Storage Function, version: 2009/09/11 [ 15.075070] LUN: removable file: (no medium) [ 15.075312] LUN: file: /dev/mmcblk0p4 [ 15.075326] Number of LUNs=1 [ 15.083453] g_mass_storage gadget: Mass Storage Gadget, version: 2009/09/11 [ 15.083479] g_mass_storage gadget: userspace failed to provide iSerialNumber [ 15.083488] g_mass_storage gadget: g_mass_storage ready [ 15.083507] dwc2 20980000.usb: bound driver g_mass_storage [ 15.203126] dwc2 20980000.usb: new device is high-speed [ 15.225530] uart-pl011 20201000.serial: no DMA platform data [ 15.582358] dwc2 20980000.usb: new device is high-speed [ 15.707818] dwc2 20980000.usb: new device is high-speed [ 15.785478] dwc2 20980000.usb: new address 1 [ 15.809602] g_mass_storage gadget: high-speed config #1: Linux File-Backed Storage
これだと15秒くらいで有効化されている。だいたい10秒くらい縮まったか。
initramfsで有効化する方法
そもそもSDカードのパーティションへのアクセスとmodprobeが使えるタイミングなら良いわけで、initramfsでやってしまえば良いのでは...?と思ったのでやってみる。
systemdのサービスは無効化しておく。
$ sudo systemctl disable storage.service
そして、/etc/initramfs-tools/hooks/usbstorage
を作る。
#!/bin/sh . /usr/share/initramfs-tools/scripts/functions . /usr/share/initramfs-tools/hook-functions manual_add_modules g_mass_storage
作った/etc/initramfs-tools/hooks/usbstorage
に実行権限を与えて、オーナーを変えておく。
$ sudo chmod +x /etc/initramfs-tools/hooks/usbstorage $ sudo chown root.root /etc/initramfs-tools/hooks/usbstorage
次に/etc/initramfs-tools/scripts/local-bottom/usbstorage
を作る。
#!/bin/sh PREREQ="" prereqs() { echo "$PREREQ" } case $1 in prereqs) prereqs exit 0 ;; esac . /scripts/functions log_begin_msg "Setting up usbstorage:" modprobe g_mass_storage file=/dev/mmcblk0p4 removable=y log_end_msg "Done..." exit 0
こちらも実行権限を与えて、オーナーを変えておく。
$ sudo chmod +x /etc/initramfs-tools/scripts/local-bottom/usbstorage $ sudo chown root.root /etc/initramfs-tools/scripts/local-bottom/usbstorage
これらを使ってinitramfsを作る。
$ sudo mkinitramfs -o /boot/initrd.gz
/boot/config.txt
を編集し、末尾に次のinitramfs有効化を追加する。
# enable initramfs initramfs initrd.gz
改めて再起動して動作を確認する。
dmesgを見ると,
[ 4.672923] Mass Storage Function, version: 2009/09/11 [ 4.672945] LUN: removable file: (no medium) [ 4.673187] LUN: removable file: /dev/mmcblk0p4 [ 4.673318] Number of LUNs=1 [ 4.679278] g_mass_storage gadget: Mass Storage Gadget, version: 2009/09/11 [ 4.679300] g_mass_storage gadget: userspace failed to provide iSerialNumber [ 4.679308] g_mass_storage gadget: g_mass_storage ready [ 4.679328] dwc2 20980000.usb: bound driver g_mass_storage [ 4.795750] dwc2 20980000.usb: new device is high-speed [ 5.172263] dwc2 20980000.usb: new device is high-speed [ 5.250174] dwc2 20980000.usb: new address 1 [ 5.274662] g_mass_storage gadget: high-speed config #1: Linux File-Backed Storage
起動してから5秒くらいで有効化されるようになった。30秒近く待たされるより速くなったと思う。
Webプログラマの手持ちスキルを駆使してRaspberry PIに電源OFFボタンをつけた
回路組んで基盤作ったりとかできないので、Raspberry PI用のLCD購入して、UI作って電源OFFボタンをつけた。
UIはLinuxのGUIアプリケーションを書く...のは回避して、ブラウザでできるようにして自分の手持ちスキル側に寄せた(Electron使うって手もあったかもだけど)。
使ったLCD
Quimatの3.5インチタッチスクリーン。
仕組み
localhostからのみアクセス可能にしたWebサーバをサービスとして動かしつつ、 GUIでユーザーを自動ログインにしておいて、電源投入されたらブラウザをkioskモードで起動してUI表示する。
UIのPower Offボタンをクリック(タッチ)したらWEB API投げてWebサーバ側でシャットダウンする。
GUI周りの環境構築
Raspberry PIのフォーラムの記事を参考にした。
ウィンドウマネージャはRPDやLXDEではなく一番シンプルなOpenboxを利用した。
Xサーバインストール
まずはフォーラムの記事の「Advanced - Custom Desktop Environment using Openbox WM」に従ってXサーバを推奨インストール無しでインストールする。
$ sudo apt-get install -y --no-install-recommends xserver-xorg
Openboxとlightdm、ブラウザとかのインストール
続いて、Openboxと自動ログインで使用するlightdm、Chromiumブラウザとフォントをインストールする。
lightdmは合わせてaccountsservice
を入れる。これを入れておかないとlightdmがError getting user list from org.freedesktop.Accounts: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.Accounts was not provided by any .service files
みたいなエラーを履いたりする。
$ sudo apt-get install -y openbox lightdm accountsservice chromium-browser fonts-noto
自動ログイン後のブラウザのデフォルト起動設定
自動ログインさせるpiユーザーの.config/openbox/autostart
を作成し、次の設定を行う。
xset -dpms & xset s off & chromium-browser --noerrdialogs --kiosk --incognito --disable-translate --fast --fast-start --disable-infobars --disable-features=TranslateUI --disk-cache-dir=/dev/null --no-first-run --no-default-browser-check --disk-cache-size=0 --media-cache-size=0 --disable-bookmark-reordering --disable-extensions --disable-dev-tools --disable-sync http://localhost:8080/ &
ディスプレイの省電力モードをオフにするためにxset -dpms
、スクリーンセーバーをオフにするためにxset s off
、chromiumはkioskモードで起動し、余計なものを色々出さないようにするためにオプションをモリモリつける。
マウスカーソルが表示されないようにする
画面にマウスカーソルが表示されないように、/etc/lightdm/lightdm.conf
を編集して#xserver-command=X
とコメントアウトされている箇所を次のように書き換える。
xserver-command=X -nocursor
自動ログインを有効化
raspi-configで3 Boot Options
->B1 Desktop / CLI
->B4 Desktop Autologin
を設定する。
動作デモ
見づらいGIFになっちゃったけど、こんな感じで動く。Raspberry PI 3 B+を使って、電源投入からブラウザの起動完了まで30秒くらい。
まとめ
フロントエンドとかバックエンドとか、諸々のソースコードはこちら。OSのイメージは前に作ったrootfsのReadOnly環境や、extraパーティションを作ったやつにした。今後もっと機能つけて遊んでみるかも。
Raspbianに追加したextrafsパーティションのためのツールを作った
以前にRaspbianのデフォルトOSイメージにextrafsパーティションを追加する手順をまとめて、スクリプト化した。
今回は追加したextrafsパーティションをSDカードの最大容量まで拡張するツール(expand-extrafs)と、SDカードの違いで起こる問題を考慮してOSイメージをエクスポートするツール(export-compact-os-image)を作った。
expand-extrafs
extrafsパーティションを任意のタイミングでSDカードの最大容量まで拡張するためのツールとして、expand-extrafsを作った。
事前にサービスとしてRaspbianに登録しておけば、bootパーティションにexpandfs
ファイルを置いてOSを起動すると自動でサイズ拡張してくれる。
これはRaspbianのSSH有効化の仕様を参考にした(あっちはsystemd使ってやっているけど)。
動かすアプリケーション環境を出荷前まで準備しておいたOSをイメージファイルとして保存しておいて、SDカードに焼いて初回起動で自動拡張する。
export-compact-os-image
SDカードからextrafsパーティションの最後までをエクスポートするコマンドとして、export-compact-os-imageを作った。
これは単純にSDカード全体をddコマンドでエクスポートしただけだと、イメージファイルのサイズがそのSDカードのサイズになってしまうことを防ぐために作成した。例えば8GBのSDカードであってもメーカー毎とか、商品毎とかでSDカードの容量が微妙に異なることがあるので、あえて小さいイメージとして作成しておけるようにして、書き込むSDカードを制限しないことが目的。
開発時や環境構築時はextrafsパーティションを拡張せずにOSを作って、このコマンドでエクスポートしてイメージファイルにする。
とりあえずこれでextrafsパーティションを使う準備ができた。
RaspbianのrootfsをReadOnlyにした
fsprotectとroot-ro
Raspberry PIでRaspbianを使っている時、rootfsのReadOnly化については、fsprotectを使った方法を過去に試したことがあった。
fsprotectはdebianパッケージとしてインストールして、/boot/cmdline.txt
にfsprotect
パラメータを追加するだけで設定できるのだけど、
Raspbianではaufsのパッチを当ててカーネルを再構築する必要があり、非力なRaspberry PIでカーネルビルドするの辛いのでPCでクロスコンパイルってことになって、前提条件を達成する作業が結構大変だった。
で、他の方法を調べていたら、最近root-roというものを知った。
root-roはoverlayfsを利用しているので、カーネルの再構築が不要で簡単に導入できる。
ただ、/boot/config.txt
にinitramfs initrd.gz
を設定して、initramfsで起動する = ReadOnly
、initramfsで起動しない = 通常
となっていて、
このあたりは常にinitramfsで起動し、cmdlineパラメータでReadOnlyの有効/無効を切り替えられるfsprotectの方が好みだった。
また、root-roは/boot
パーティションもデフォルトでReadOnlyにしていて、
fsprotectは追加設定で/boot
もReadOnlyにできるがデフォルトではなっていないという違いがあった。
fsprotectを使っていた時はデフォルトのままで使っていたので、rootfsのReadOnlyを一時的に解除したいときは/boot/cmdline.txt
をちょっと編集して再起動するだけで良かった。
root-roもremountして/mnt/boot-ro
を編集すれば良いという手はあるが、/boot
ではなく/mnt/boot-ro
を意識する必要があり、個人的には/boot
はReadOnlyじゃないほうが好みだった。
で、どうせRaspbianでしか使うつもりないしということもあり、好みを満たすために1から自分で作ってみた。
readonlyfs
作ったのはこれ。
仕組みはoverlayfsを利用してinitramfsでrootfsのマウントをtmpfsを使ったupperdir、lowerdir、workdirを組み合わせたものに取り替えるというもの。
ReadOnlyの有効/無効の切り替えはcmdlineパラメータにreadonlyfs
があるかどうかで行う。
とりあえずこれを適用したものとしていないものを用意して、Diskへの書き込みをログ取りしてみた。
ReadOnly化していないRaspbianでは次のようになり、Diskへの書き込みが発生していることがわかる。
ReadOnly化したRaspbianでは次のようになり、Diskへの書き込みが発生していないことがわかる。
最低限の実装なので、tmpfsのサイズ決めたり、/boot
パーティションをReadOnlyにする機能はつけていない。
とりあえずこれでしばらく様子見。
RaspbianのOSイメージのパーティションを分割した
動機
公式で配布されているRaspbian OSイメージはbootとrootfsのパーティションのみで構成されていて、 Raspberry PIに入れて起動するとrootfsが自動でSDカード全体まで拡張される。
そのまま常時稼働で運用していると、OSのログやtmpやswapの利用でSDカードへの書き込みがチクチク行われて、 NANDフラッシュの書き込み上限を迎えてある日ディスクが壊れることになる。
NANDフラッシュはSLC、MLC、TLCで書き込み上限が異なるので、なるべく上限の多いタイプを選ぶべきだけど、 それでも上限があることには変わりないし、工業用のSLCのSDカードとか結構高価。
なのでなるべくディスクへの書き込みは最小限にしたい。
また、電源ぶち切りとか停電とか考えると、そもそもrootfsはReadOnlyにしたい。
ただし、ReadOnlyにすると設定ファイルの書き換えみたいな最低限の書き込みや変更ができなくなってしまうので、 書き込みができる領域が別にほしいということになる。
というわけで、公式のRaspbian OSイメージをカスタマイズして、bootとrootfsの他にもう一つパーティションを追加する手順をまとめた。
作業環境とRaspbian OSイメージのバージョン
作業環境は今回もLinux PC。
Raspbianのイメージは(もう新しいの出てるけど)2018-11-13-raspbian-stretch-lite.img
を利用した。
カスタマイズ手順
作業は次のように行った。
カスタマイズ用のOSイメージを作ってパーティションの状況確認
まずカスタマイズ用に公式イメージのコピーを作成した。
$ cp 2018-11-13-raspbian-stretch-lite.img 2018-11-13-raspbian-stretch-lite-custom.img
次にfdiskで現在のイメージのパーティション状況を確認した。
$ fdisk -l 2018-11-13-raspbian-stretch-lite-custom.img Disk 2018-11-13-raspbian-stretch-lite-custom.img: 1.8 GiB, 1866465280 bytes, 3645440 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x7ee80803 デバイス 起動 Start 最後から セクタ Size Id タイプ 2018-11-13-raspbian-stretch-lite-custom.img1 8192 98045 89854 43.9M c W95 FAT32 (LBA) 2018-11-13-raspbian-stretch-lite-custom.img2 98304 3645439 3547136 1.7G 83 Linux
デバイス2018-11-13-raspbian-stretch-lite-custom.img1
がbootパーティションで、
2018-11-13-raspbian-stretch-lite-custom.img2
がrootfsパーティションに当たる。
OSイメージのファイルサイズを増やす
パーティションを追加できるようにするために、まずtruncateを使ってOSイメージのファイルサイズを増やす。
$ truncate -s 4GiB 2018-11-13-raspbian-stretch-lite-custom.img
とりあえず4GBまで増やした。
改めてfdiskでイメージの状況を確認する。
$ fdisk -l 2018-11-13-raspbian-stretch-lite-custom.img Disk 2018-11-13-raspbian-stretch-lite-custom.img: 4 GiB, 4294967296 bytes, 8388608 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x7ee80803 デバイス 起動 Start 最後から セクタ Size Id タイプ 2018-11-13-raspbian-stretch-lite-custom.img1 8192 98045 89854 43.9M c W95 FAT32 (LBA) 2018-11-13-raspbian-stretch-lite-custom.img2 98304 3645439 3547136 1.7G 83 Linux
イメージのサイズが1.8GBから4GBまで増えたことが確認できる。
rootfsのパーティションサイズを変更
次にrootfsのパーティションサイズを拡張する。
fdiskの対話モードでカスタムイメージを開いて、dコマンドで一度rootfsのパーティションを削除し、nコマンドで改めて作りなおす。
この時新たに作るパーティションのFirst sectorはもともとの値(今回であれば98304)を指定する。
Last sectorは今回はrootfsを3GB固定にするので+3G
を指定した。
$ fdisk 2018-11-13-raspbian-stretch-lite-custom.img Welcome to fdisk (util-linux 2.27.1). Changes will remain in memory only, until you decide to write them. Be careful before using the write command. コマンド (m でヘルプ): d パーティション番号 (1,2, default 2): 2 Partition 2 has been deleted. コマンド (m でヘルプ): n Partition type p primary (1 primary, 0 extended, 3 free) e extended (container for logical partitions) Select (default p): p パーティション番号 (2-4, default 2): 2 First sector (2048-8388607, default 2048): 98304 Last sector, +sectors or +size{K,M,G,T,P} (98304-8388607, default 8388607): +3G Created a new partition 2 of type 'Linux' and of size 3 GiB.
作りなおしたrootfsパーティションをpコマンドで確認すると次のようになる。
コマンド (m でヘルプ): p Disk 2018-11-13-raspbian-stretch-lite-custom.img: 4 GiB, 4294967296 bytes, 8388608 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x7ee80803 デバイス 起動 Start 最後から セクタ Size Id タイプ 2018-11-13-raspbian-stretch-lite-custom.img1 8192 98045 89854 43.9M c W95 FAT32 (LBA) 2018-11-13-raspbian-stretch-lite-custom.img2 98304 6389759 6291456 3G 83 Linux
書き込み用領域のパーティションを追加
次に書き込み領域にする予定のパーティションをnコマンドで追加する。
First sectorは2018-11-13-raspbian-stretch-lite-custom.img2
の最後のセクタ + 1の値(今回であれば6389760)とし、
Last sectorは指定せずにイメージファイルのサイズいっぱいまでを使う。
コマンド (m でヘルプ): n Partition type p primary (2 primary, 0 extended, 2 free) e extended (container for logical partitions) Select (default p): p パーティション番号 (3,4, default 3): 3 First sector (2048-8388607, default 2048): 6389760 Last sector, +sectors or +size{K,M,G,T,P} (6389760-8388607, default 8388607): Created a new partition 3 of type 'Linux' and of size 976 MiB.
追加したパーティションをpコマンドで確認する。
コマンド (m でヘルプ): p Disk 2018-11-13-raspbian-stretch-lite-custom.img: 4 GiB, 4294967296 bytes, 8388608 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x7ee80803 デバイス 起動 Start 最後から セクタ Size Id タイプ 2018-11-13-raspbian-stretch-lite-custom.img1 8192 98045 89854 43.9M c W95 FAT32 (LBA) 2018-11-13-raspbian-stretch-lite-custom.img2 98304 6389759 6291456 3G 83 Linux 2018-11-13-raspbian-stretch-lite-custom.img3 6389760 8388607 1998848 976M 83 Linux
wコマンドでパーティションの変更を反映してfdiskの対話モードを抜ける。
コマンド (m でヘルプ): w The partition table has been altered. Syncing disks.
書き込み用領域のフォーマットとラベル付け
追加したパーティションはまだフォーマットしていないので、フォーマットするためにイメージをLinux PCでマウントできるようにする。 これにはkpartxを利用してイメージファイル内のパーティションをデバイスにマッピングする。
$ sudo kpartx -av 2018-11-13-raspbian-stretch-lite-custom.img add map loop0p1 (253:0): 0 89854 linear 7:0 8192 add map loop0p2 (253:1): 0 6291456 linear 7:0 98304 add map loop0p3 (253:2): 0 1998848 linear 7:0 6389760
各パーティションは/dev/mapper
下にマッピングされる。
$ ls /dev/mapper control loop0p1 loop0p2 loop0p3
/dev/mapper/loop0p1
がboot、/dev/mapper/loop0p2
がrootfs、/dev/mapper/loop0p3
が今回追加したパーティションに当たる。
/dev/mapper/loop0p3
をEXT4でフォーマットする。
$ sudo mkfs -t ext4 /dev/mapper/loop0p3 mke2fs 1.42.13 (17-May-2015) Discarding device blocks: done Creating filesystem with 249856 4k blocks and 62464 inodes Filesystem UUID: 828efd40-40d9-42f9-85f7-5d39b1154840 Superblock backups stored on blocks: 32768, 98304, 163840, 229376 Allocating group tables: done Writing inode tables: done Creating journal (4096 blocks): done Writing superblocks and filesystem accounting information: done
わかりやすいようにパーティションにラベルをつける。
$ sudo e2label /dev/mapper/loop0p3 extrafs
ラベルがついたか確認。
$ sudo e2label /dev/mapper/loop0p3 extrafs
rootfsの自動リサイズを無効化・書き込み用領域のマウントを設定
bootとrootfsのファイルに手を加えるために、それぞれのマウントポイントを作成する。
$ mkdir boot $ mkdir rootfs
bootをマウントする。
$ sudo mount /dev/mapper/loop0p1 boot
rootfsをマウントする。
$ sudo mount /dev/mapper/loop0p2 rootfs
boot/cmdline.txt
を編集し、末尾のinit=/usr/lib/raspi-config/init_resize.sh
を削除して、初回起動時のrootfsの自動拡張を無効にする。
$ sudo vi boot/cmdline.txt`
編集後のboot/cmdline.txt`は次のようになる。
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=7ee80803-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet
rootfs/etc/fstab
を編集し、extrafsパーティションが起動時にマウントされるように設定する。
末尾に/dev/mmcblk0p3 /extra ext4 defaults,noatime 0 1
を追加する。
$ sudo vi rootfs/etc/fstab
編集後のrootfs/etc/fstab
は次のようになる。
proc /proc proc defaults 0 0 PARTUUID=7ee80803-01 /boot vfat defaults 0 2 PARTUUID=7ee80803-02 / ext4 defaults,noatime 0 1 /dev/mmcblk0p3 /extra ext4 defaults,noatime 0 1
他のパーティションがPARTUUID指定なのでちょっと歪だけどとりあえずこれで行く。
rootfs内にextrafs用のマウントポイントを追加しておく。
$ sudo mkdir rootfs/extra
bootとrootfsをアンマウントする。
$ sudo umount boot $ sudo umount rootfs
$ sudo kpartx -d 2018-11-13-raspbian-stretch-lite-custom.img
カスタムイメージの動作確認
出来上がったカスタムイメージをSDカードに書き込む。
$ sudo dd if=2018-11-13-raspbian-stretch-lite-custom.img of=/dev/mmcblk0 bs=1M 4096+0 レコード入力 4096+0 レコード出力 4294967296 bytes (4.3 GB, 4.0 GiB) copied, 776.849 s, 5.5 MB/s
書き込み完了したSDカードをRaspberry PIに入れて起動する。
起動したRaspbianにログインし、fdiskでパーティションの状況を確認する。
$ sudo fdisk -l /dev/mmcblk0 Disk /dev/mmcblk0: 7.4 GiB, 7969177600 bytes, 15564800 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x7ee80803 Device Boot Start End Sectors Size Id Type /dev/mmcblk0p1 8192 98045 89854 43.9M c W95 FAT32 (LBA) /dev/mmcblk0p2 98304 6389759 6291456 3G 83 Linux /dev/mmcblk0p3 6389760 8388607 1998848 976M 83 Linux
mountの状況を確認する。
$ mount /dev/mmcblk0p2 on / type ext4 (rw,noatime,data=ordered) devtmpfs on /dev type devtmpfs (rw,relatime,size=217616k,nr_inodes=54404,mode=755) sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime) proc on /proc type proc (rw,relatime) tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev) devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000) tmpfs on /run type tmpfs (rw,nosuid,nodev,mode=755) tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k) tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755) cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd) cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct) cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio) cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls) cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer) sunrpc on /run/rpc_pipefs type rpc_pipefs (rw,relatime) debugfs on /sys/kernel/debug type debugfs (rw,relatime) systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=35,pgrp=1,timeout=0,minproto=5,maxproto=5,direct) mqueue on /dev/mqueue type mqueue (rw,relatime) configfs on /sys/kernel/config type configfs (rw,relatime) /dev/mmcblk0p1 on /boot type vfat (rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro) /dev/mmcblk0p3 on /extra type ext4 (rw,noatime,data=ordered) tmpfs on /run/user/1000 type tmpfs (rw,nosuid,nodev,relatime,size=44384k,mode=700,uid=1000,gid=1000)
/dev/mmcblk0p3 on /extra type ext4 (rw,noatime,data=ordered)
となっていれば/extra
に追加したパーティションがマウントされていることが確認できる。
まとめ
とりあえず分割までできた。
この作業をいちいち手でやるのが面倒になったので、作成用スクリプト作った。
$ sudo ./create-extra-partition <path to>/YYYY-MM-DD-raspbian-stretch-lite.img
みたいな感じで作成できる。