ミルク色の記録

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

Webプログラマが業務でIoTデバイス開発をして思ったこと

自分は本来Webプログラマのつもりなのだけれど、会社のやんごとなき事情により、今年はRaspberry PIやESP32を使ったいわゆるIoTデバイスアプリの開発をすることが多かった(あと、Kotlin使ってAndroidモバイルアプリ開発もやった)。

もともと、時々調査手伝ったり、プロトタイプ的なデバイスアプリ作ったりはしていたので、全く未経験ってわけではなかったけれど、自分一人で全部やって納品まで持っていったのは初めてだった。

少ないハードウェアリソースの中で専門的にやっている人たちに比べれば、自分のしたことはママゴトみたいなものなのだろうけど、開発を通して知ったこと・気づいたことがあったので記録として残しておく。

知ったこと・気づいたこと

ボード上で直接コードを変更しない

Raspberry PIやBeagle BoneでPython使ってアプリ作る時の話。ボードのOSがLinuxなので書こうと思えばSSHで入って直接書くことはできる。特にGPIOとかSPIとかI2Cみたいなボードの入出力使う必要がある場合、PCだとできない動作確認をする都合もあって、ついついソースコードを直接編集したくなる。

でも、ボードの方で編集しているとPCの方に持ってくるのを忘れてたり、うっかりボードのOS固有のファイルパスとかをハードコーディングしてしまって移植性が落ちるみたいなことになるので、「PCでコード書いて、ボードに転送して動作確認」を徹底したほうが良い。

自分はlsyncdを非デーモンモードで動かしてファイル変更したら自動転送して作っていた。

PythonPyPIモジュールはAPTで入れない

GPIO触るやつとか、OpenCVまわりとか、APTで直接OSに入れられるものもあるけど、requirements.txtに載っていない依存になったりして後々混乱するはめになるので、pipでインストールするほうが良い。最近はpiwheelsでも配布されていたりして、意外と大丈夫だったりする。

requirements.txtに載ってないけど、コード上はimportしているみたいなのはOSへのインストール忘れてたりして危なっかしくなる。

バイスOSの環境セットアップはスクリプトにしておく

Python3のvenvであったりPyPIモジュールが依存するOSの共有ライブラリなどのインストール、ちょっとした環境設定などはシェルスクリプトでセットアップスクリプトにしておくと良い。

ただし、どんなケースでも動くセットアップスクリプトを目指すと大変になるので、実行できる設定ドキュメントぐらいのノリで書いておくと良い。

shやbashで書くのが辛くなったら、いっそのことpython3で書いてしまえばいい。標準ライブラリの範囲内であればOSのプリインストール範囲内で結構できる。

国際化設定はちゃんと設定しておく

言語設定、タイムゾーン設定、キーボードレイアウト設定はデフォルトではen_GB環境になっているので、ちゃんと設定変えておいたほうが良い(en_GBな環境が対象ならばそのままでいいけど)。Wifi使うならWifiの地域設定も同様。後からキーボード繋いでちょっと調査...みたいなときに日本語キーボード繋いだらレイアウトあってなくて大混乱とかマジ大変。

いちいちraspi-configで設定するのが面倒ならば一通り設定するスクリプトを作っておけばいい。

raspi-configのコード見ればどのファイル編集すればいいか・コマンド叩けばいいかは判断できる。

OS(Raspbian)のリリース情報はこまめにチェックする

Raspbian、結構な頻度でバージョンアップするので公式のリリースはチェックして、チェンジログとか見ておいたほうが良い。rootじゃなくても実行できる権限増えてたり、ハードウェア乱数生成ツールがプリインストール対象にされていたりするし、OverlayFS利用したファイルシステムの読み込み専用化みたいな「コレもう標準でできるんじゃん」が結構出てきたりする。

ボード自体のアップデート(Raspberry PI 4のリリースとか)があると、旧バージョンのディストリだともう動かないとか出てくるので、最新リリースのディストリ(現在はBuster)と一つ前の(現在はStretch)は抑えて置くくらいの感覚でいたほうが良い。

ただし、ディストリの新バージョンが出た直後だとまだちょっと安定してない...?みたいな動作をする時があるので、先は見すえつつ採用は慎重にみたいな感じ。

ネットで見つかる環境設定方法などは鮮度に注意する

バイス開発に限った話じゃないけれど。

たとえばRTCの設定方法とか、「それもうdtoverlayでできるよ」みたいなのが見つかったり、自分でコマンド実装して叩いているやつがサービスとしてすでに入っていたりするので、ネットで見つけた情報は参考にしつつも公式の情報にないか調べてみると良い。

雑な見分け方として/etc/rc.localに実行コマンド足して何かしている系は、より良いやり方見つけられることが多い。

udevやsystemdなど活用してOSでできることはOSでやる

USBカメラをつないだら/dev/cameraシンボリックリンクができるようにudevルールを設定しておいて、/dev/video[0-9]+を直接探さなくても良いようにするとか、アプリケーションが動く前に確実にNTP同期が取れているようにsystemdの依存関係をうまく組み合わせて動かすとか。

アプリケーションに何でも仕事を持たせすぎずにOSでできることはOSに任せたほうが良い。

時計はマジで重要

HTTPSの接続とかWifiの証明書つかった認証とか、システムクロックがあっていないと途端にうまくいかなくなることがたくさんあるので、時刻があっていることを重視したほうが良い。

NTP頼みならば同期が確実に取れるまで待機させるとか、可能であればRTCの搭載を積極的に行うとか。とにかく時計は大事。

工夫すればテストは意外と書ける

以前はデバイスアプリの開発はどうしてもハードウェアへの依存が多くなるので、単体テストとか書きにくそうだなーと思っていた。 ただ、Webアプリケーションを使う場合でも、テストしにくいもの(データベースとか、ネットワーク越しの外部APIとか)を使うことになるモジュールをテストしやすくする方法はあるので、結局同じことかなーと思って工夫してみた。

UARTを使うpyserialや、SPI用のspidev、I2C用のsmbus-cffiとか、ビジネスロジックの中でなるべく直接使わずに、薄くラップしてインターフェイスを意識して利用することで分離し、ビジネスロジックをハードウェアの異存なしにテストできるようにした。

簡単な例で書くと説明しにくいのだけれど、例えば次のようにAppクラスのコンストラクタでシリアルコネクションを生成するとテストし難いコードになる。

import serial

class App(object):

    def __init__(self, serial_port):
        self._serial_conn = serial.Serial(serial_port, 115200, timeout=1)
        # その他初期化処理...

    def run(self):
        b = self._serial_conn.read(self._serial_conn.in_waiting or 1)
        if b:
            # 読み取りバッファを使った何らかの処理...

そこで、コンストラクタではシリアルコネクションを受け取って使うようにして、依存を外に追い出す。これだけでシリアルコネクションをモックに差し替えてテスト可能になる(実際はpyserialそのまま使わずに、もっと抽象化したものを使う)。

import serial

class App(object):

    def __init__(self, serial_conn):
        self._serial_conn = serial_conn
        # その他の初期化処理...

                                                                                                                                                                    
    def run(self):
        b = self._serial_conn.read(self._serial_conn.in_waiting or 1)
        if b:
            # 読み取りバッファを使った何らかの処理...

この辺りはRobert C.Martin先生のClean CodeとかClean Architectureとか読んだことが活かせた気がする(そういえばハードウェアを抽象化した話もあったような気がする)。

Pythonの場合だけではなくて、ESP32用にC++で書いていた時もAuniterでテストするにあたって同じようにやれた。

SDカードからイメージを作るときはサイズに注意

同じ8GBや16GBのSDカードだからといって、実際の細かいサイズまで同じとは限らない(メーカーの違いとかで変わる)。 8GBのSDカードに焼いたOSをddコマンドで取り出して、別のSDカードに焼こうとして容量不足で書けないとかザラにある。

SDカードの容量いっぱいまで使わないサイズにパーティションを意識して作成して、ddで取り出す時もcountオプションでパーティションの終わりまで切って取り出したほうが良い。

モバイルバッテリーで動かすときは消費電力に注意が必要

電源取れない場所で使うデバイスでモバイルバッテリー使うことになってわかったけど、一般のモバイルバッテリーにはオートパワーオフ機能がついていて、一定以上の電力消費が無いと供給をやめてしまう機能が付いている。

で、困ったことにこの基準となる消費電力のスペックがあまり公開されていない(Ankerの場合、日本法人のWebサイトにはなく、本家のWebサイトに載っていた)ので、満たせるかどうか綱渡り感がある。

とくにESP32は消費電力を抑えようとしてディープスリープ入れたりすると供給止められちゃうので要注意。

ESP32のディープスリープ時の時計は当てにならない

ESP32のディープスリープで起床時間を指定して眠りに入る機能があるけど、ディープスリープ時の時計が結構ずれやすく、長く寝させれば寝させるほど予定の時間に起きなかったりする。

しょうが無いのでRTCのDS3231を積んで、DS3231に付いているアラーム機能でWake upの信号を入れるようにして正しく起床させた。やはり時計は重要。

Arduinoの開発するならVSCodeがおすすめ

ESP32の開発はArduino IDEでできるんだけど、VS CodeArduino用拡張当てて使うほうが断然開発効率が良い。 シリアルでの入力だけはArduino IDEのほうがよい。

Arch Wikiは役にたつ

何かと行き着くArch Wiki。困った時に読むと情報載ってたりして役に立つ。 Raspberry PI用のページもあって非常に良い。

まとめ

他にもあった気がするけど、ざっとこんな感じ。

本業の方のWebプログラミングはさっぱり進歩がなくて、会社の他のメンバーのほうがやってたんじゃないかなーくらいな状態で、正直なところ焦ってる。将来を考えた上で非常にまずい感じなので、今後は「デバイス開発も素人程度にはできるWebプログラマ」になれるように努めていきたい。

raspi-configのAdvanced OptionsでOverlay FSを使ってみた

きっかけ

先日、メモリの設定を変えようとraspi-configを起動して、Advanced Optionsを開いたらメニューにOverlay FSが増えていることに気がついた。

f:id:ushiboy:20191215130640p:plain
Advanced Options

説明にEnable/Disable read-only file systemとあるので、これって/パーティションを読み込み専用にするやつじゃね?と思い、素のRaspbian OSイメージをSDカードに焼いて試してみた。

試したイメージはRaspbian Buster Lite 2019-09-26でAPTでパッケージを最新に上げたもの。

Overlay FS の有効化

Overlay FSを選択すると、有効にするか聞かれるので、Yesを選択する。

f:id:ushiboy:20191215131141p:plain

すると、initramfsの生成が行われ、完了メッセージが表示される。

f:id:ushiboy:20191215131318p:plain

次に/bootパーティションを書き込み保護するか聞かれる。Yesを選択した場合は/bootパーティションが読み込み専用になり、Noにした場合/bootパーティションはそのままとなる。

f:id:ushiboy:20191215131426p:plain

とりあえずNoを選択して実行すると、/bootの扱いについて表示される。

f:id:ushiboy:20191215131657p:plain

このあとraspi-configを終了すると再起動を促されるので、OSを再起動してログインし直す。

ログイン後、mount状態を見ると次のようになる。

$ mount
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
proc on /proc type proc (rw,relatime)
udev on /dev type devtmpfs (rw,nosuid,relatime,size=465128k,nr_inodes=116282,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /run type tmpfs (rw,nosuid,noexec,relatime,size=94832k,mode=755)
overlay on / type overlay (rw,noatime,lowerdir=/lower,upperdir=/upper/data,workdir=/upper/work)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
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)
cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
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/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=29,pgrp=1,timeout=0,minproto=5,maxproto=5,direct)
mqueue on /dev/mqueue type mqueue (rw,relatime)
sunrpc on /run/rpc_pipefs type rpc_pipefs (rw,relatime)
debugfs on /sys/kernel/debug type debugfs (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)
tmpfs on /run/user/1000 type tmpfs (rw,nosuid,nodev,relatime,size=94828k,mode=700,uid=1000,gid=1000)

overlay on / type overlay (rw,noatime,lowerdir=/lower,upperdir=/upper/data,workdir=/upper/work)とあり、/パーティションがoverlayfsで保護されているのがわかる。

この時、/boot/cmdline.txtは次のようになっている。

boot=overlay console=serial0,115200 console=tty1 root=PARTUUID=6c586e13-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait

先頭にboot=overlayが追加されている。また、/boot/config.txtの末尾には次のようにinitramfs initrd.img-4.19.75-v7+-overlayが追加されている。

[all]
#dtoverlay=vc4-fkms-v3d
initramfs initrd.img-4.19.75-v7+-overlay

Overlay FS の無効化

raspi-configのAdvanced OptionsOverlay FSを選択し、disableにすればよい。再起動後にマウントの状態は次のように戻る。

$ mount
/dev/mmcblk0p2 on / type ext4 (rw,noatime)
devtmpfs on /dev type devtmpfs (rw,relatime,size=469544k,nr_inodes=117386,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)
cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=33,pgrp=1,timeout=0,minproto=5,maxproto=5,direct)
sunrpc on /run/rpc_pipefs type rpc_pipefs (rw,relatime)
mqueue on /dev/mqueue type mqueue (rw,relatime)
debugfs on /sys/kernel/debug type debugfs (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)
tmpfs on /run/user/1000 type tmpfs (rw,nosuid,nodev,relatime,size=94828k,mode=700,uid=1000,gid=1000)

/bootパーティションの書き込み保護

Overlay FSを有効化するときに/bootパーティションの書き込み保護も有効にすると、再起動後にマウント状態は次のようになる。

$ mount
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
proc on /proc type proc (rw,relatime)
udev on /dev type devtmpfs (rw,nosuid,relatime,size=465128k,nr_inodes=116282,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /run type tmpfs (rw,nosuid,noexec,relatime,size=94832k,mode=755)
overlay on / type overlay (rw,noatime,lowerdir=/lower,upperdir=/upper/data,workdir=/upper/work)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
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)
cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
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 (ro,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro)
tmpfs on /run/user/1000 type tmpfs (rw,nosuid,nodev,relatime,size=94828k,mode=700,uid=1000,gid=1000)

/dev/mmcblk0p1 on /boot type vfat (ro,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro)となっており、/bootパーティションが読み込み専用(ro)になっていることがわかる。

これは/etc/fstabの設定によって行っているようで、/etc/fstabを見ると次のようになっている。

$ cat /etc/fstab 
proc            /proc           proc    defaults          0       0
PARTUUID=6c586e13-01  /boot           vfat    defaults,ro          0       2
PARTUUID=6c586e13-02  /               ext4    defaults,noatime  0       1
# a swapfile is not a swap partition, no line here
#   use  dphys-swapfile swap[on|off]  for that

PARTUUID=6c586e13-01 /boot vfat defaults,ro 0 2とあり、roが設定されていることがわかる。

ちなみに/bootの書き込み保護も有効にすると、Overlay FSの解除はraspi-configの1度の実行だけでは駄目で、2度行う必要がある。1度目の解除実行時に下のようなメッセージがでる。

f:id:ushiboy:20191215132927p:plain

想像だけど、一度目の解除実行では/bootのマウントを書き込み可で再マウントして、/boot/cmdline.txt/boot/config.txtを修正してOverlay FSを無効化するが、/etc/fstabOverlay FSの解除後でないと変更できないからとかじゃないかと思う。

気になること

initramfsのファイル名をinitrd.img-4.19.75-v7+-overlayという感じで-overlay付きで作っているけど、カーネルバージョンアップデート時の自動的なinitramfsの作り直しに対応できているのかな?というのはちょっと気になったりもする。

まとめ

Raspberry PIでなにも対策せずに動かしているとSDカード壊れるのは昔からの課題だったので、公式にOverlay FSで読み込み専用にできるようになったのは素直に嬉しい。

/は読み込み専用にしつつ特定のディレクトリは書き込み可にしておきたいということで、パーティションを分割する需要は今後も増えそう。

Raspberry PIでRTC(DS3231)を使った

Raspberry PIでなんやかんややっていると、HTTPSWifiの証明書あり認証とかで時計の重要性に気がつくことが多い。

で、NTPで確実に同期取れるの待つsystemd用サービスとか作って対処したりしていたけど、やっぱリアルタイムクロックあったほうが良いよなーと思ったので使い方を覚えてみることにした。

使ったRTCモジュール

Amazonでも売ってたこれを使った。モジュールはDS3231。I2Cで接続するもの。

試したOS

Raspbian Buster Liteの2019-09-26バージョンでパッケージを最新まで上げたやつ。

設定方法

まずraspi-configでI2Cを有効にする。

続いて、fake-hwclockを無効化する。

$ sudo systemctl disable fake-hwclock
$ sudo apt-get -y remove fake-hwclock

RTCへの時刻設定とRTCからシステムクロックへの反映については、ざっと調べた感じudevの/lib/udev/rules.d/85-hwclock.rulesが次のようになっていて、rtcデバイスを見つけたら/lib/udev/hwclock-setを実行してくれるみたい。

# Set the System Time from the Hardware Clock and set the kernel's timezone
# value to the local timezone when the kernel clock module is loaded.

KERNEL=="rtc0", RUN+="/lib/udev/hwclock-set $root/$name"

ただし、/lib/udev/hwclock-setはデフォルトだと/run/systemd/systemを見つけて処理せずに中断してしまう様子。

#!/bin/sh
# Reset the System Clock to UTC if the hardware clock from which it
# was copied by the kernel was in localtime.

dev=$1

if [ -e /run/systemd/system ] ; then
    exit 0
fi

if [ -e /run/udev/hwclock-set ]; then
    exit 0
fi

if [ -f /etc/default/rcS ] ; then
    . /etc/default/rcS
fi

# These defaults are user-overridable in /etc/default/hwclock
BADYEAR=no
HWCLOCKACCESS=yes
HWCLOCKPARS=
HCTOSYS_DEVICE=rtc0
if [ -f /etc/default/hwclock ] ; then
    . /etc/default/hwclock
fi

if [ yes = "$BADYEAR" ] ; then
    /sbin/hwclock --rtc=$dev --systz --badyear
    /sbin/hwclock --rtc=$dev --hctosys --badyear
else
    /sbin/hwclock --rtc=$dev --systz
    /sbin/hwclock --rtc=$dev --hctosys
fi

# Note 'touch' may not be available in initramfs
> /run/udev/hwclock-set

なので次のように/run/systemd/systemのチェックをコメントアウトした。

#if [ -e /run/systemd/system ] ; then
#    exit 0
#fi

で、あとはRTCをOS起動時にI2Cで有効化するだけなのだけど、検索してみると/etc/rc.localやsystemdなどでコマンドを実行するやり方を見かける。 例えば/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.

echo ds3231 0x68 > /sys/class/i2c-adapter/i2c-1/new_device

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

exit 0

echo ds3231 0x68 > /sys/class/i2c-adapter/i2c-1/new_deviceを追加して、DS3231をRTCとして認識させるわけだけど、これでやるとログが次のようになる。

Dec  7 16:48:03 raspberrypi systemd-modules-load[169]: Inserted module 'i2c_dev'
Dec  7 16:48:03 raspberrypi systemd[1]: Starting Flush Journal to Persistent Storage...
Dec  7 16:48:03 raspberrypi systemd[1]: Started Create Static Device Nodes in /dev.
Dec  7 16:48:03 raspberrypi systemd[1]: Starting udev Kernel Device Manager...
Dec  7 16:48:03 raspberrypi systemd[1]: Started Set the console keyboard layout.
Dec  7 16:48:03 raspberrypi systemd[1]: Started Flush Journal to Persistent Storage.
Dec  7 16:48:03 raspberrypi systemd[1]: Reached target Local File Systems (Pre).
Dec  7 16:48:03 raspberrypi systemd[1]: Started udev Coldplug all Devices.

****** 長いのでちょっと省略 ******

Dec  7 16:48:11 raspberrypi systemd[1]: Starting /etc/rc.local Compatibility...
Dec  7 16:48:11 raspberrypi systemd[1]: Condition check resulted in fast remote file copy program daemon being skipped.
Dec  7 16:48:11 raspberrypi kernel: [   40.700114] i2c i2c-1: new_device: Instantiated device ds3231 at 0x68
Dec  7 16:48:11 raspberrypi systemd[1]: Starting Permit User Sessions...
Dec  7 16:48:11 raspberrypi kernel: [   40.838077] rtc-ds1307 1-0068: registered as rtc0
Dec  7 16:48:11 raspberrypi systemd[1]: Starting OpenBSD Secure Shell server...
Dec  7 16:48:11 raspberrypi systemd[1]: Started /etc/rc.local Compatibility.
Dec  7 16:48:12 raspberrypi systemd[1]: Started Permit User Sessions.
Dec  7 16:48:12 raspberrypi systemd[1]: Started Serial Getty on ttyAMA0.
Dec  7 16:48:12 raspberrypi systemd[1]: Started Getty on tty1.
Dec  7 16:48:12 raspberrypi systemd[1]: Reached target Login Prompts.
Dec  7 22:20:06 raspberrypi systemd[1]: Starting Daily apt download activities...
Dec  7 22:20:06 raspberrypi systemd[1]: Started OpenBSD Secure Shell server.
Dec  7 22:20:06 raspberrypi avahi-daemon[304]: Server startup complete. Host name is raspberrypi-3.local. Local service cookie is 410738562.

OSが起動してから、ディスクに保存されていた最終日時でログが記録されていき、/etc/rc.localが実行されてからようやく正しい日時で記録されるようになる。なので、/etc/rc.localが動く前の日時は当てにならない。

そこで、/boot/overlays/READMEに次のようにRTCの例が書いてあるので、これに従って設定する。今回はds1307をds3231に置き換えるだけで良い。

Using Overlays
==============

Overlays are loaded using the "dtoverlay" config.txt setting. As an example,
consider I2C Real Time Clock drivers. In the pre-DT world these would be loaded
by writing a magic string comprising a device identifier and an I2C address to
a special file in /sys/class/i2c-adapter, having first loaded the driver for
the I2C interface and the RTC device - something like this:

    modprobe i2c-bcm2835
    modprobe rtc-ds1307
    echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device

With DT enabled, this becomes a line in config.txt:

    dtoverlay=i2c-rtc,ds1307

This causes the file /boot/overlays/i2c-rtc.dtbo to be loaded and a "node"
describing the DS1307 I2C device to be added to the Device Tree for the Pi. By
default it usees address 0x68, but this can be modified with an additional DT
parameter:

    dtoverlay=i2c-rtc,ds1307,addr=0x68

Parameters usually have default values, although certain parameters are
mandatory. See the list of overlays below for a description of the parameters
and their defaults.

/boot/config.txtdtoverlay=i2c-rtc,ds3231を追加して保存する。

これでOSを起動すると、ログは次のようになる。OSの起動の頭からRTCが効いて正確に日時が扱えているのがわかる。

Dec  7 22:25:47 raspberrypi systemd-modules-load[157]: Inserted module 'i2c_dev'
Dec  7 22:25:47 raspberrypi systemd[1]: Starting Flush Journal to Persistent Storage...
Dec  7 22:25:47 raspberrypi systemd[1]: Started Apply Kernel Variables.
Dec  7 22:25:47 raspberrypi systemd[1]: Started Flush Journal to Persistent Storage.
Dec  7 22:25:47 raspberrypi systemd[1]: Started Create Static Device Nodes in /dev.
Dec  7 22:25:47 raspberrypi systemd[1]: Starting udev Kernel Device Manager...
Dec  7 22:25:47 raspberrypi systemd[1]: Started Set the console keyboard layout.
Dec  7 22:25:47 raspberrypi systemd[1]: Reached target Local File Systems (Pre).
Dec  7 22:25:47 raspberrypi systemd[1]: Started udev Coldplug all Devices.
Dec  7 22:25:47 raspberrypi systemd[1]: Starting Helper to synchronize boot up for ifupdown...
Dec  7 22:25:47 raspberrypi systemd[1]: Started Helper to synchronize boot up for ifupdown.
Dec  7 22:25:47 raspberrypi systemd[1]: Started udev Kernel Device Manager.

****** あとは省略 ******

/boot/overlays/READMEにはI2CのRTCについて次のように記述されている。ここに載っているモジュールであれば同様のやり方で設定できると思う。

Name:   i2c-rtc
Info:   Adds support for a number of I2C Real Time Clock devices
Load:   dtoverlay=i2c-rtc,<param>=<val>
Params: abx80x                  Select one of the ABx80x family:
                                  AB0801, AB0803, AB0804, AB0805,
                                  AB1801, AB1803, AB1804, AB1805

        ds1307                  Select the DS1307 device

        ds1339                  Select the DS1339 device

        ds3231                  Select the DS3231 device

        m41t62                  Select the M41T62 device

        mcp7940x                Select the MCP7940x device

        mcp7941x                Select the MCP7941x device

        pcf2127                 Select the PCF2127 device

        pcf2129                 Select the PCF2129 device

        pcf8523                 Select the PCF8523 device

        pcf8563                 Select the PCF8563 device

        rv3028                  Select the Micro Crystal RV3028 device

        addr                    Sets the address for the RTC. Note that the
                                device must be configured to use the specified
                                address.

        trickle-diode-type      Diode type for trickle charge - "standard" or
                                "schottky" (ABx80x only)

        trickle-resistor-ohms   Resistor value for trickle charge (DS1339,
                                ABx80x, RV3028)

        wakeup-source           Specify that the RTC can be used as a wakeup
                                source

        backup-switchover-mode  Backup power supply switch mode. Must be 0 for
                                off or 1 for Vdd < VBackup (RV3028 only)

Niigata.js #2に行ってきた

niigata-js.connpass.com

正式ナンバリングとしては2回目とのこと。今回も参加。

新潟で行けるJavaScriptコミュニティはありがたいので、勢いで発表込みで参加申込んだけど、最近どんどんJavaScriptから遠ざかってるのでネタは結構迷った。

で、結局今でもまとめやすいE2Eテストネタで発表してきた。

www.slideshare.net

鮮度イマイチだし、JavaScript味が少なめですまんかったという感じ。

他の参加者さんの発表はtypeormやReact Hooks、TensorFlow.js、npx、UIフレームワークの話と言った感じだった。まだ着いていける話題で良かった...と内心ホッとしたことに気づいて、やっぱ最近遠ざかってるから地味に焦ってるなーと思った。

今後は開催頻度上げて行きたいとの話もあったので、個人的ななんやかんやとは向き合いつつ、また参加したい。

MOBILE CREW NIIGATA 2019に行ってきた

10月11日に行われたMOBILE CREW NIIGATA 2019に行ってきた。

www.mobilecrew.jp

参加までの経緯

Twitterで流れてきて開催を知って、最初は「モバイルアプリ系かー」と思っていたけど、後々上がってきたトークセッション見てたら、アプリアーキテクチャ概論とか、地方IT企業の戦略を広げる技術選択とか、PWA×SPAとか、いくつか興味あったので参加を決めた。

とりあえず個人で申し込んでいたら会社の方から「業務で行っていいよ」と言われたけど、個人的な都合と、あとちょっとした想いみたいなのもあったりなかったりで、今回は個人参加のままにした。

当日の雰囲気とか会場

平日の有料イベントで新潟で人集まるのかな...と内心思っていたが、結構参加者いっぱいいて驚いた。県外から来ている方もいたみたいだし、学生さんとか、なんか若い人多かった(オッサン感)。

当日の朝までしっかり確認していなかったけど、会場が駅直結なのでアクセス良かった。

ヤスダヨーグルトとか雪室珈琲のサービスは地方色出てておもしろかった。

セッションの感想

フラーとANDROIDアプリの共創ヒストリー

「アプリやWebを作ることが目的ではない。」という良い話から始まって、花火アプリのオフライン化でFirestoreの機能をうまく使っているなという印象。

東京で働くことと新潟との違いのところで出てきた「東京の学生はずるい」は同感。

新しい技術で最小でやりたいことを実現できないかを模索してやっていく姿勢が良かった。

LIVEDATA WITH DATABINDING実用レポート

AndroidJetpackから始まって、DataBindingやLiveData、ViewModelとかの話。 Android知識ほとんど無いマンなので、良さそうなの色々あるのねーという印象だった。

アプリアーキテクチャ概論

アーキテクチャが勝手に決まることはなく、チーム・組織やドメインなどの背景・要因から最適なアーキテクチャを選定するという大事なお話。

技術的変化を随時取り入れていき、停滞して追従を困難にさせない「ジャンプ台を作らない」という表現が良かった。

迷ったら標準的手法を選ぶの大事。

地方IT企業の戦略を広げる技術選択としてのREACT NATIVE

B2B2Bの特性か、エンドユーザーが使っているスマートフォンがなんなのかわからない(使っている本人がわかっていない)ケースがあり、 AndroidiOSでUIが同じ見た目であることに価値があったという経験談が納得感高かった。

人を集め難い地方で少ない人数でどのようにクロスプラットフォーム開発をやっていくかという説得力のある話だった。

考察 : モバイルエンジニアの機械学習との付き合い方

モデルを使おう!という話w。

iOSのCreate ML、Core MLがめっちゃ簡単ですごく便利。Playgrounds(だったっけ?)でちょろっとコード書いたらモデル生成のためのUI出てきて、教師データ食わせて作れるのがすげー。

業務的なことで機械学習まわり調べたことがあって、あやふやな理解のまま画像集めてなにやらpythonのコード組み合わせて作ったことあったけど、導入がこれだけ楽だといいなーと思った。

簡易なものであれば簡単にモデルを作れるし、作ったor既存のモデルを使って機能を作ってく時代がきた感じ。

決定論から確率論での結果取得に頭切り替える必要がありそう。

WEBベースでアプリライクなUI/UXが実現できる「PWA×SPA」という選択肢

3文字用語多すぎWeb世界を簡潔に整理した話。

PWAに夢を抱いているマンなのにLighthouseやAppscopeとか知らんかった。なので、セッションのあとで以前作ったPWAなやつの採点こっそりやって見てた。

SPAは独学で業務・趣味ともにゴリゴリやってきてたけど、App Shellモデルというのは知らんかった。

INTEGRATE YOUR APP TO MODERN WORLD

SwiftUI良さそうなのにiOS 13からしか使えない、でも備えていくにはどうするかというお話。

小さいコンポーネントにしてちょっとずつ移行できるようにする、SwiftUIを意識したUIKitでのコード・設計。

iOS知識もほとんど無いマンなのだけど、なるほどうまいという印象だった。

長岡花火を支える技術

長岡花火アプリの舞台裏的な、Firebaseへのデータ反映をBitriseやGithubを組み合わせた運用でやってたという話。

トーク後に出た、「スプレッドシートにしてGoogle App Scriptでもできたのでは?」という良質問の回答で、 CSV形式でGithubにあげて、プルリクエストでレビューしたうえでBitrise経由で反映するためで、 レビューを入れることに重点を置いたというのが納得感があった。

管理画面の実装に重きを置くわけにはいかないので、組み合わせで実現するが、レビューをいれて品質を上げるやりかたはすごくうまい。

トークセッション テーマ1:アプリ開発者のキャリアプラン & テーマ2:モバイルアプリ開発の技術選択

前半首都圏の話と後半地方の話みたいな印象。メモ取らずに聴き入ってたがいい話で面白かった。

全体を通して

技術カンファレンスって首都圏とかの開催ばっかで(福岡とか元気なところもあるけど)、参加するとなると一大決心、ホテルを決めて、新幹線決めて頑張って朝一でGo!みたいな感じなので、常々「首都圏は良いよな...」と思ってた。正直なところ、東京の学生だけでなくて、社会人も羨ましかった(新潟にいたいから住んでるんだけど)。

そんな中で新潟で参加できるカンファレンスとういことで、本当にありがたかった。

運営の三社さんは新潟の中でも(隣の芝感抜きにしても)元気ある感じで、新潟にある会社っぽくないなーと思ってたけど、そんな会社さんだから実現できたのかなと思いつつ、実現・運営にあたってはただの参加者だった自分には想像できない大変さとかあったと思うので、本当に感謝の気持ちしかない。

個人的なこと

欲を言ってしまうと、もっとWebの話も聴ける機会があるといいなって思った。

自分はAndroidの人でもなければiOSの人でもなく、一応Webの人なつもりだったのだけど、最近はめっきりシェルスクリプトPythonC++しか書いてなくて、Raspberry PIやESP32でなんだかんだみたいなことしかしていない。けど、やっぱりGoogle Mapに衝撃を受けてからずっとWebが好きなんだよなーと途中のセッションでGoogle Mapのことがでてきて改めて思った。とりあえずPWAの分野は完全に自分の理想なので、たとえ業務でできなくてももっと勉強していきたい。

あと、会場の椅子に座ってたら結構すぐに腰が痛くなってしまい、セッション終わる毎に会場でてストレッチしてた。まじで「これが老化か...?」と思った。もっと筋肉つけないと...。

最後に

運営の皆様、スピーカーの皆様ありがとうございました。

Raspbianのinitramfsをカーネルのアップデートに追従するようにした

Raspberry PIでrootfsのReadOnly化やUSB OTGとかで試してきて、ちょっとだけinitramfs使ってやりたいことやれるようになった。

だけど、apt-get upgradeカーネルが更新されると、既存のinitramfsを作りなおさなければならないことに気がついた。 しばらくはカーネルの更新があるたびに手作業で作りなおしていたんだけど、これなんとかならんかな…と思っていた。

ある日PCのubuntuでパッケージの更新をしていた時に、流れてくる出力にinitramfsを作りなおしているらしき表示があることに気がついた。 で、もしかしてaptの更新で動くフックがあるんじゃないかと思って探してみた。

対象はRaspbian Busterの2019-07-10のやつ。

initramfs-toolsのフック

目的のフックは/etc/kernel/postinst.d/initramfs-toolsにあった。

中身は次のようになっていた。

#!/bin/sh -e

version="$1"
bootopt=""

command -v update-initramfs >/dev/null 2>&1 || exit 0

# passing the kernel version is required
if [ -z "${version}" ]; then
        echo >&2 "W: initramfs-tools: ${DPKG_MAINTSCRIPT_PACKAGE:-kernel package} did not pass a version number"
        exit 2
fi

# exit if kernel does not need an initramfs
if [ "$INITRD" = 'No' ]; then
        exit 0
fi

# absolute file name of kernel image may be passed as a second argument;
# create the initrd in the same directory
if [ -n "$2" ]; then
        bootdir=$(dirname "$2")
        bootopt="-b ${bootdir}"
fi

# avoid running multiple times
if [ -n "$DEB_MAINT_PARAMS" ]; then
        eval set -- "$DEB_MAINT_PARAMS"
        if [ -z "$1" ] || [ "$1" != "configure" ]; then
                exit 0
        fi
fi

# we're good - create initramfs.  update runs do_bootloader
# shellcheck disable=SC2086
INITRAMFS_TOOLS_KERNEL_HOOK=1 update-initramfs -c -k "${version}" ${bootopt} >&2

ざっと見た感じ、この仕組みに乗っかるならupdate-initramfsで作ったほうが良さそう(自分はmkinitramfsコマンドで作っていた)。 とりあえずmkinitramfsで作っておいたinitrd.imgupdate-initramfs -c -k $(uname -r)で作りなおした。

ファイル名はinitrd.img-x.y.z-v7+のようになるので、/boot/config.txtinitramfs設定も修正しておいた。

また、スクリプトを見るに$INITRDの値が設定されていないと更新はキャンセルされそう。 そこでこの値を設定する方法を探した。これは/etc/default/raspberrypi-kernelにあった。

中身こんな感じ。

# Defaults for raspberrypi-kernel

# Uncomment the following line to enable generation of
# /boot/initrd.img-KVER files (requires initramfs-tools)

#INITRD=Yes

# Uncomment the following line to enable generation of
# /boot/initrd(7).img files (requires rpi-initramfs-tools)

#RPI_INITRD=Yes

rpi-initramfs-toolsってなんだ?と思ったけど、特にそういうパッケージがあるわけではないみたい。 今のところ取り置きされているような感じなのだろうか…?とりあえずINITRD=Yesコメントアウトを解除した。 その状態でapt-get upgradeカーネルの更新をしてやると、出力に次のような表示が出てきて新しいカーネルバージョンでinitramfsが作りなおされていることが解る。

run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 4.19.66+ /boot/kernel.img
run-parts: executing /etc/kernel/postinst.d/initramfs-tools 4.19.66+ /boot/kernel.img
/etc/kernel/postinst.d/initramfs-tools:
update-initramfs: Generating /boot/initrd.img-4.19.66+
run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 4.19.66-v7+ /boot/kernel7.img
run-parts: executing /etc/kernel/postinst.d/initramfs-tools 4.19.66-v7+ /boot/kernel7.img
/etc/kernel/postinst.d/initramfs-tools:
update-initramfs: Generating /boot/initrd.img-4.19.66-v7+
run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 4.19.66-v7l+ /boot/kernel7l.img
run-parts: executing /etc/kernel/postinst.d/initramfs-tools 4.19.66-v7l+ /boot/kernel7l.img
/etc/kernel/postinst.d/initramfs-tools:
update-initramfs: Generating /boot/initrd.img-4.19.66-v7l+

実際に/bootの中身を見てみると

$ ls /boot/initrd.img-*
/boot/initrd.img-4.19.66+  /boot/initrd.img-4.19.66-v7+  /boot/initrd.img-4.19.66-v7l+

となって、新たなバージョンのinitramfsができていることがわかった(手で作った旧バージョンは削除されていた)。

ただし、Raspbianの場合このままだと/boot/config.txtinitramfs設定は古いままになってしまうので、 そこはひと手間かけてやる必要がありそう。

/etc/kernel/postinst.d/initramfs-toolsをカスタマイズ

ひとまず/etc/kernel/postinst.d/initramfs-toolsの末尾に次の処理を追加した。

# replace /boot/config.txt
current_version="$(uname -r)"
old_version_number=${current_version%-*}
if echo $version | grep -Pq "^\d+\.\d+\.\d+\+$"; then
        old_version="$old_version_number+"
else
        old_version="$old_version_number-${version#*-}"
fi
sed -i "s/initramfs initrd.img-$old_version/initramfs initrd.img-$version/g" "$bootdir/config.txt"

現在動作しているOSのバージョンを調べて更新対象のライブラリバージョンで/boot/config.txtinitramfs設定を書き換える。

これで改めて確認し、Busterではアップデートに追従できるようになった。

Stretchに対応する

上記の修正をStretchでもやってみたところ、次のような出力が表示されて作りなおしに失敗した。

run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 4.19.66+ /boot/kernel.img
run-parts: executing /etc/kernel/postinst.d/initramfs-tools 4.19.66+ /boot/kernel.img
/etc/kernel/postinst.d/initramfs-tools:
update-initramfs: Generating /boot/initrd.img-4.19.66+
run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 4.19.66-v7+ /boot/kernel7.img
run-parts: executing /etc/kernel/postinst.d/initramfs-tools 4.19.66-v7+ /boot/kernel7.img
/etc/kernel/postinst.d/initramfs-tools:
update-initramfs: Generating /boot/initrd.img-4.19.66-v7+
run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 4.19.66-v7l+ /boot/kernel7l.img
run-parts: executing /etc/kernel/postinst.d/initramfs-tools 4.19.66-v7l+ /boot/kernel7l.img
/etc/kernel/postinst.d/initramfs-tools:
update-initramfs: Generating /boot/initrd.img-4.19.66-v7l+
WARNING: missing /lib/modules/4.19.66-v7l+
Ensure all necessary drivers are built into the linux image!
depmod: ERROR: could not open directory /lib/modules/4.19.66-v7l+: No such file or directory
depmod: FATAL: could not search modules: No such file or directory
depmod: WARNING: could not open /var/tmp/mkinitramfs_L5yf77/lib/modules/4.19.66-v7l+/modules.order: No such file or directory
depmod: WARNING: could not open /var/tmp/mkinitramfs_L5yf77/lib/modules/4.19.66-v7l+/modules.builtin: No such file or directory

*** 中略 ***

Processing triggers for initramfs-tools (0.130) ...
ln: failed to create hard link '/boot/initrd.img-4.19.66-v7l+.dpkg-bak' => '/boot/initrd.img-4.19.66-v7l+': Operation not permitted
cp: error writing '/boot/initrd.img-4.19.66-v7l+.dpkg-bak': No space left on device
dpkg: error processing package initramfs-tools (--configure):
 subprocess installed post-installation script returned error exit status 1
Processing triggers for libc-bin (2.24-11+deb9u4) ...
Errors were encountered while processing:
 initramfs-tools
E: Sub-process /usr/bin/dpkg returned an error code (1)

メッセージを読みながら調べてみると、更新処理がRaspberry PI4用のv7l+系のinitramfsを作ろうとするが、 StretchはRaspberry PI4に対応していないのでv7l+用のモジュールライブラリが見つからずにdepmodのエラーが出ている。失敗に終わったinitrd.img-4.19.66-v7l+ファイルはそのまま/bootに残留する。 更にバックアップを取って新しく作ろうとするので、/bootパーティションのディスクスペースが圧迫されて作成できずに落ちる…ということらしい。

そこで/etc/kernel/postinst.d/initramfs-tools$INITRDのチェックの次に、下のような処理を追加した。

# skip ignore version lib
if [ ! -e "/lib/modules/$version" ]; then
        echo "Not exist module libraries. [$version]"
        exit 0
fi

これでv7l+のような対応していないライブラリバージョンの生成はスキップされる。

また、/bootパーティションの空き容量が深刻なので、/usr/sbin/update-initramfsbackup_initramfsbackup_booted_initramfsの方にも手を入れた。

# backup initramfs while running
backup_initramfs()
{
        [ ! -r "${initramfs}" ] && return 0
        #initramfs_bak="${initramfs}.dpkg-bak"
        initramfs_bak="/var/tmp/$(basename initramfs).dpkg-bak"
        [ -r "${initramfs_bak}" ] && rm -f "${initramfs_bak}"
        #ln -f "${initramfs}" "${initramfs_bak}" \
        #       || cp -a "${initramfs}" "${initramfs_bak}"
        mv "${initramfs}" "${initramfs_bak}"
        verbose "Keeping ${initramfs_bak}"
}

# keep booted initramfs
backup_booted_initramfs()
{
        #initramfs_bak="${initramfs}.dpkg-bak"
        initramfs_bak="/var/tmp/$(basename initramfs).dpkg-bak"

        *** 以下略 ***

デフォルトでは/bootパーティションに作成されていたバックアップファイルを/var/tmpに作成するように変更し、 既存バージョンのファイルを直接バックアップファイルにすることで、既存バージョンのファイルと、バックアップファイル、新しく作成するファイルで一時的に3つできてしまうことを避けるようにした。

これでapt-get upgradeカーネルの更新をして結果は次のようになった。

run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 4.19.66+ /boot/kernel.img
run-parts: executing /etc/kernel/postinst.d/initramfs-tools 4.19.66+ /boot/kernel.img
/etc/kernel/postinst.d/initramfs-tools:
update-initramfs: Generating /boot/initrd.img-4.19.66+
run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 4.19.66-v7+ /boot/kernel7.img
run-parts: executing /etc/kernel/postinst.d/initramfs-tools 4.19.66-v7+ /boot/kernel7.img
/etc/kernel/postinst.d/initramfs-tools:
update-initramfs: Generating /boot/initrd.img-4.19.66-v7+
run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 4.19.66-v7l+ /boot/kernel7l.img
run-parts: executing /etc/kernel/postinst.d/initramfs-tools 4.19.66-v7l+ /boot/kernel7l.img
/etc/kernel/postinst.d/initramfs-tools:
Not exist module libraries. [4.19.66-v7l+]

** 以下省略 **

4.19.66-v7l+用のライブラリは存在しないのでスキップされ、/bootパーティション内でファイル容量食い過ぎることもないので、エラーになることなくinitramfsの更新までできた。

パッチファイル

それぞれのバージョン用に作ったパッチをインストールスクリプトともにリポジトリに置いておいた。

github.com

RaspbianでOS再起動せずにネットワーク再起動で設定反映できるようにした

Raspbianのいつのバージョンだったか忘れたけど、固定IPアドレスの設定を/etc/dhcpcd.confに行うようになったあたりで、変更したネットワーク設定をサービス再起動で適用する方法がよくわからなくなった。

とりあえずdhcpcd.confで設定しているのだからdhcpcdを再起動してみたが反映されない。

$ sudo systemctl restart dhcpcd
Warning: The unit file, source configuration file or drop-ins of dhcpcd.service changed on disk. Run 'systemctl daemon-reload' to reload units.

警告が出ているので、daemon-reloadを行ってからdhcpcdを再起動してみたが、警告がでなくなっただけで反映されない。

$ sudo systemctl daemon-reload
$ sudo systemctl resart dhcpcd

networkingを再起動してみても反映されない。

$ sudo systemctl restart networking

OS再起動すれば反映されるけど、ネットワーク設定の変更だけでOSの再起動したくないので、何か手はないかと調べていたらフォーラムで次の記事を見つけた。

lb.raspberrypi.org

どうやらip addr flush devで一度インターフェイスの設定を飛ばしてあげれば良いらしい。

フォーラムの記事ではPythonスクリプト書かれていたけど、参考にしてシェルスクリプトでコマンドを作ってみた。

restart-resize コマンド

全てのインターフェイスの再起動だけではなくて、指定したインターフェイスだけの再起動もできるようにした。

書いたスクリプトは次の通り。

#!/bin/bash
#
# restart-network applies network interface configuration changes without restarting the OS.
# This implementation is based on the method described in the following forum article:
#
#   https://lb.raspberrypi.org/forums/viewtopic.php?t=199860
#
# [Usage]
#
#   * When restarting all network interfaces (the lo interface is excluded)
#
#       $ sudo restart-network
#
#   * When restarting the specified network interface
#
#       $ sudo restart-network eth0
#

check_privilage()
{
    if [ "`whoami`" != "root" ]; then
        echo "Require root privilege"
        exit 1
    fi
}

stop_dhcpcd()
{
    systemctl daemon-reload
    systemctl stop dhcpcd
}

flush_ip_address()
{
    ip addr flush dev $1
}

flush_ip_addresses()
{
    DEVICES=(`ls /sys/class/net`)
    for DEVICE in ${DEVICES[@]}
    do
        if [ "$DEVICE" != "lo" ]; then
            flush_ip_address $DEVICE
        fi
    done
}

start_dhcpcd()
{
    systemctl start dhcpcd
}

check_privilage

TARGET="all"

if [ $# -eq 1 ]; then
    TARGET=$1
fi

stop_dhcpcd

if [ $TARGET == "all" ]; then
    flush_ip_addresses
else
    flush_ip_address $TARGET
fi

start_dhcpcd

これを使って、全てのインターフェイスを再起動する場合は次のように実行する。

$ sudo restart-network

指定したインターフェイスだけ再起動する場合は次のように実行する。

$ sudo restart-network eth0

これでいちいちOS再起動しなくて良くなった。