Linux でデバイスを接続している時だけ動くサービスを作る
systemd と udev を使うと、とあるデバイスを接続すると動き始め、切断すると停止するようなサービスを作る事が出来る。
サンプルのサービスは何でも良いが 前に作った /usr/bin/timeserver.py を使う。
#!/usr/bin/env python
# Time server program
import socket
import datetime
import sys
HOST = '' # Symbolic name meaning all available interfaces
PORT = 50007 # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(1)
while True:
conn, addr = s.accept()
now = datetime.datetime.now().isoformat()
print('Connected by %s at %s\n' % (addr, now))
conn.sendall(now.encode())
conn.close()
まず、サービスの動作に必要な動作を整理する。USB 機器としては、たまたま手元にあった USB BT ドングル (BT アダプタ) を使う。
- 標準で起動する (WantedBy で multi-user.true -> timeserver への依存を作る)
- BT アダプタが有効になるまで待つ。(systemd の After を使う)
- BT アダプタが無効になれば停止する。(systemd の BindsTo を使う)
- BT アダプタが再び有効になれば再起動する。(WantedBy で デバイス -> timeserver への依存を作る)
Systemd の世界では、「BT アダプタが存在する」事を示す unit がある。このようにデバイスに依存する unit を device と呼ぶ。systemctl list-units -t device
で定義済のリストを見る事が出来る。
# systemctl list-units -t device
UNIT LOAD ACTIVE SUB DESCRIPTION
...
sys-devices-pci0000:00-0000:00:11.0-0000:02:00.0-usb2-2\x2d2-2\x2d2.1-2\x2d2.1:1.0-bluetooth-hci0.device loaded active plugged /sys/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/bluetooth/hci0
...
sys-subsystem-bluetooth-devices-hci0.device loaded active plugged /sys/subsystem/bluetooth/devices/hci0
...
BT アダプタを表す hci0 device は /lib/udev/rules/99-systemd.rules で定義されている。
SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_ALIAS}+="/sys/subsystem/bluetooth/devices/%k"
これは、もしも SUBSYSTEM=="bluetooth"
にマッチするイベントが来たら TAG+="systemd"
によって systemd device unit を作成し、ついでに ENV{SYSTEMD_ALIAS}+="/sys/subsystem/bluetooth/devices/%k"
によってエイリアスを作成するという意味だ。%k は hci0
に置換されるので、unit 名としては sys-subsystem-bluetooth-devices-hci0.device が出来上がる。
自分の監視したいデバイス用の unit が既存の udev ルールにお定義されていなければ自分でも定義出来る。
この unit を BindsTo, After, WantedBy に指定して /etc/systemd/system/timeserver.service を書けば良い。
[Unit]
Description=Some Time server
After=network.target
# 2. Stop the service when the BT is disconnected
BindsTo=sys-subsystem-bluetooth-devices-hci0.device
# 3. Wait until BT is connected
After=sys-subsystem-bluetooth-devices-hci0.device
[Service]
ExecStart=/usr/bin/python -u /usr/bin/timeserver.py
Restart=always
StandardOutput=journal
StandardError=journal
[Install]
# 1. Start by default
WantedBy=multi-user.target
# 4. Start when BT is connected
WantedBy=sys-subsystem-bluetooth-devices-hci0.device
[Install]
セクションに書いた定義は systemctl enable
を行うとはじめて有効になる。
# systemctl daemon-reload
# systemctl enable timeserver
Created symlink /etc/systemd/system/multi-user.target.wants/timeserver.service → /etc/systemd/system/timeserver.service.
Created symlink /etc/systemd/system/sys-subsystem-bluetooth-devices-hci0.device.wants/timeserver.service → /etc/systemd/system/timeserver.service.
設定ファイルが書けたら start してみる。BT アダプタが無い場合は BT アダプタを接続するまで処理が止まる。その後 BT アダプタと連動して timeserver が再生停止を行う。
# systemctl start timeserver
... BT アダプタを挿すまで停止
# journalctl -f -u timeserver # timeserver の状態を観察
Oct 26 03:28:36 qemux86-64 systemd[1]: Started Some Time server.
Oct 26 03:28:49 qemux86-64 systemd[1]: Stopping Some Time server...
Oct 26 03:28:49 qemux86-64 systemd[1]: Stopped Some Time server.