ミルク色の記録

やったこと、やってみたこと

Raspberry PIでNTPの同期が取れてからアプリケーションのサービスを起動するようにした

Raspberry PIにはRTC(リアルタイムクロック)が搭載されていないので、ボード上に日時を保存しておいてOSのシステム時計に反映する方法はデフォルトではない。

ただ、昨今のRaspbianはsystemd-timesyncdによって、ファイルシステムに最後の日時を保存しておいてそこから復元し、その後NTPで同期するみたいな動きになっている。なので、OSを起動すると毎回とりあえず1970年から開始…みたいな感じにはならない。

systemd-timesyncd はネットワークを介してシステム時刻を同期させるために追加されたデーモンです。 Raspberry Pi や組み込みデバイスなどの RTC を載せてないシステムのために、新しい NTP の同期が取得される度にディスクに現在の時刻を保存し、それを使って起動時にシステム時刻を修正することができ、時刻が常に正しいわけではないときでも、それらのシステムで時刻がモノトニックに進むことを保証します。

引用: systemd-timesyncd - ArchWiki

ただし、これだと自分で作ったアプリケーションをいつ起動するのかによってシステム時刻の正確な日時が取れるタイミングが微妙な感じになる。

サンプルアプリケーションで検証してみる

たとえば、次のようなアプリケーションを用意する。起動すると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を作った。

github.com

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で公開した。

github.com

CORSとは

Cross-Origin Resource Sharing (CORS) は、追加の HTTP ヘッダーを使って、あるオリジン (ドメイン) で動いているウェブアプリケーションが、異なるオリジンのサーバーのリソースにアクセスできるようにする仕組み

引用: MDN Web docs オリジン間リソース共有 (CORS)

雑に説明すると、サーバ側で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に指定できる文字コードの種類はPHPmbstringがサポートしてるやつ。

補足

認証キーとオリジンでアクセス制限するようにしているけど、これは気休め程度。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のように直接接続できるので、いろいろな機器に応用できる。

引用: USB On-The-Go - Wikipedia

調べてみたら手持ちのRaspberry PI B系では構造上できないようなので、Raspberry PI Zero WHを買ってみた。送料とか色々考えてとりあえずAmazonでポチリ。

シリアル通信したり、ネットワークにしたり、キーボードにしたりと色々できるみたいだけど、とりあえず今回はストレージとして使う方法を試してみた。

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はLinuxGUIアプリケーションを書く...のは回避して、ブラウザでできるようにして自分の手持ちスキル側に寄せた(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 offchromiumkioskモードで起動し、余計なものを色々出さないようにするためにオプションをモリモリつける。

マウスカーソルが表示されないようにする

画面にマウスカーソルが表示されないように、/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秒くらい。

f:id:ushiboy:20190608135913g:plain
動作デモ

まとめ

フロントエンドとかバックエンドとか、諸々のソースコードはこちら。OSのイメージは前に作ったrootfsのReadOnly環境や、extraパーティションを作ったやつにした。今後もっと機能つけて遊んでみるかも。

github.com

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を起動すると自動でサイズ拡張してくれる。

f:id:ushiboy:20190504132840p:plain
expand-extrafs

これはRaspbianのSSH有効化の仕様を参考にした(あっちはsystemd使ってやっているけど)。

動かすアプリケーション環境を出荷前まで準備しておいたOSをイメージファイルとして保存しておいて、SDカードに焼いて初回起動で自動拡張する。

export-compact-os-image

SDカードからextrafsパーティションの最後までをエクスポートするコマンドとして、export-compact-os-imageを作った。

これは単純にSDカード全体をddコマンドでエクスポートしただけだと、イメージファイルのサイズがそのSDカードのサイズになってしまうことを防ぐために作成した。例えば8GBのSDカードであってもメーカー毎とか、商品毎とかでSDカードの容量が微妙に異なることがあるので、あえて小さいイメージとして作成しておけるようにして、書き込むSDカードを制限しないことが目的。

開発時や環境構築時はextrafsパーティションを拡張せずにOSを作って、このコマンドでエクスポートしてイメージファイルにする。

f:id:ushiboy:20190504132923p:plain
export-compact-os-image

とりあえずこれでextrafsパーティションを使う準備ができた。

RaspbianのrootfsをReadOnlyにした

fsprotectとroot-ro

Raspberry PIでRaspbianを使っている時、rootfsのReadOnly化については、fsprotectを使った方法を過去に試したことがあった。

github.com

fsprotectはdebianパッケージとしてインストールして、/boot/cmdline.txtfsprotectパラメータを追加するだけで設定できるのだけど、 Raspbianではaufsのパッチを当ててカーネルを再構築する必要があり、非力なRaspberry PIでカーネルビルドするの辛いのでPCでクロスコンパイルってことになって、前提条件を達成する作業が結構大変だった。

で、他の方法を調べていたら、最近root-roというものを知った。

github.com

root-roはoverlayfsを利用しているので、カーネルの再構築が不要で簡単に導入できる。

ただ、/boot/config.txtinitramfs initrd.gzを設定して、initramfsで起動する = ReadOnlyinitramfsで起動しない = 通常となっていて、 このあたりは常に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

作ったのはこれ。

github.com

仕組みはoverlayfsを利用してinitramfsでrootfsのマウントをtmpfsを使ったupperdir、lowerdir、workdirを組み合わせたものに取り替えるというもの。

ReadOnlyの有効/無効の切り替えはcmdlineパラメータにreadonlyfsがあるかどうかで行う。

とりあえずこれを適用したものとしていないものを用意して、Diskへの書き込みをログ取りしてみた。

ReadOnly化していないRaspbianでは次のようになり、Diskへの書き込みが発生していることがわかる。

f:id:ushiboy:20190427100905p:plain
ReadOnlyなし

ReadOnly化したRaspbianでは次のようになり、Diskへの書き込みが発生していないことがわかる。

f:id:ushiboy:20190427100949p:plain
ReadOnlyあり

最低限の実装なので、tmpfsのサイズ決めたり、/bootパーティションをReadOnlyにする機能はつけていない。

とりあえずこれでしばらく様子見。

RaspbianのOSイメージのパーティションを分割した

動機

公式で配布されているRaspbian OSイメージはbootとrootfsのパーティションのみで構成されていて、 Raspberry PIに入れて起動するとrootfsが自動でSDカード全体まで拡張される。

そのまま常時稼働で運用していると、OSのログやtmpやswapの利用でSDカードへの書き込みがチクチク行われて、 NANDフラッシュの書き込み上限を迎えてある日ディスクが壊れることになる。

NANDフラッシュはSLC、MLCTLCで書き込み上限が異なるので、なるべく上限の多いタイプを選ぶべきだけど、 それでも上限があることには変わりないし、工業用の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/loop0p3EXT4でフォーマットする。

$ 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に追加したパーティションがマウントされていることが確認できる。

まとめ

とりあえず分割までできた。

この作業をいちいち手でやるのが面倒になったので、作成用スクリプト作った。

github.com

$ sudo ./create-extra-partition <path to>/YYYY-MM-DD-raspbian-stretch-lite.img

みたいな感じで作成できる。