Linux でデバイスを接続している時だけ動くサービスを作る

Linux でデバイスを接続している時だけ動くサービスを作る
=================================================

systemd と udev を使うと、とあるデバイスを接続すると動き始め、切断すると停止するようなサービスを作る事が出来る。

サンプルのサービスは何でも良いが [前に作った /usr/bin/timeserver.py](https://qiita.com/propella/items/ed30de77bb0908fc5c05) を使う。

```
#!/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 アダプタ) を使う。

1. 標準で起動する (WantedBy で multi-user.true -> timeserver への依存を作る)
2. BT アダプタが有効になるまで待つ。(systemd の After を使う)
3. BT アダプタが無効になれば停止する。(systemd の BindsTo を使う)
4. 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](https://github.com/systemd/systemd/blob/v235/rules/99-systemd.rules.in#L47) で定義されている。

```
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.
```

## 参考

* [Restart= in combination with BindsTo= ineffective on device rediscovery](https://github.com/systemd/systemd/issues/2824#issuecomment-210396058)